NextAuth.js callbackUrl and first attempt after sign in - url

I am implementing NextAuth authentication and there is something that remains unclear for me. I explain.
To perform authentication with CredentialsProviders and signIn() well (means avoid the session's status unauthenticated during the first attempt). The doc gives a partial solution which is:
You can use useSession() in a way that makes sure you always have a valid session
I use the default behavior to satisfy this advice by implementing my signIn() method like this
const login_user = async () => {
const response = await signIn("Credentials", {
redirect: false,
username: username,
password: password,
});
if (response?.error) {
setError(response.error);
} else {
setError(null);
}
//If user signed successfully we redirect to the dashboard page
if (response.url && response.ok === true) {
router.push("/dashboard/general");
}
};
and it works well.
Now always in the doc, they say that we may add callbackUrl to signIn() which is the url where you want to redirect the user after successful sign in.
I configure my redirect callback in [...nextauth].ts file like this
redirect: async ({ url, baseUrl }) => {
return url.startsWith(baseUrl)
? Promise.resolve(url)
: Promise.resolve(baseUrl);
},
and modify signIn() method by adding it callbackUrl like below in the react component.
const login_user = async () => {
const response = await signIn("Credentials", {
redirect: false,
username: username,
password: password,
callbackUrl: "/dashboard/general",
});
if (response?.error) {
setError(response.error);
} else {
setError(null);
}
};
But when a user sign in, he is not redirected.
My question is:
What is the role of callbackUrl at that place if after the sign in or first attempt of sign in it cannot redirect the user to the specified callbackUrl?
I know the importance of callbackUrl about security against attacker)
May be I do not understand well that notion.
Can someone explains it?

Related

Electron browserWindow password prompt

I have a simple electron app that wraps around a web app. The web app prompts for user name but electron doesn't show the prompt and directly goes to the 401 Authorization Required page. Is there a setting I need to change to make the prompt show? I can't seem to find it in the documentation. Any help is appreciated.
const { app, BrowserWindow } = require('electron');
function createWindow() {
browserWindow = new BrowserWindow({});
browserWindow.loadURL('https://domain')
}
app.on('ready', createWindow);
Listen to this "login" event.
Create your own prompt. For example, create a browser window which loads an HTML form and when the user fills the username and password fields pass the credentials back via ipc calls to the callback.
app.on("login", (event, webContents, request, authInfo, callback) => {
event.preventDefault();
createAuthPrompt().then(credentials => {
callback(credentials.username, credentials.password);
});
});
function createAuthPrompt() {
const authPromptWin = new BrowserWindow();
authPromptWin.loadFile("auth-form.html"); // load your html form
return new Promise((resolve, reject) => {
ipcMain.once("form-submission", (event, username, password) => {
authPromptWin.close();
const credentials = {
username,
password
};
resolve(credentials);
});
});
}

Rails/Redux/Devise - Trying to log in user but along the way the value of currentUser becomes HTML string of the document

I'm working on a Rails/React/Redux app using Devise for user authentication. There's something fishy going on somewhere in between my login function and my receiveCurrentUser action where the currentUser becomes changed to an HTML string of the entire document. Here's what happens:
I first dispatch this login function:
export const login = (user) => (dispatch) => {
debugger
return SessionAPIUtil.login(user)
.then(currentUser => {
dispatch(receiveCurrentUser(currentUser));
return currentUser;
},
errors => {
dispatch(receiveSessionErrors(errors.responseJSON));
return errors;
});
};
Putting in a debugger I saw that my user was what it should be:
{username: "test_user", password: "password123"}
A debugger in my login util function (ajax call) still has user as what it should be.
export const login = user => {
debugger
return $.ajax({
method: 'POST',
url: '/users/sign_in',
data: { user }
});
};
However, when it returns from the ajax call to the ".then(..." of the first login function and we enter the receiveCurrentUser:
export const receiveCurrentUser = (currentUser) => {
debugger
return {
type: RECEIVE_CURRENT_USER,
currentUser
};
};
a debugger reveals that currentUser is an HTML string of the document:
Any insight or advice would be greatly appreciated!

Asp.net MVC - How to check session expire for Ajax request

We are using Ajax call across the application- trying to find out a global solution to redirect to login page if session is already expired while trying to execute any Ajax request. I have coded following solution taking help from this post - Handling session timeout in ajax calls
NOT SURE WHY IN MY CARE EVENT "HandleUnauthorizedRequest" DOES NOT GET FIRED.
Custom Attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CheckSessionExpireAttribute :AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var url = new UrlHelper(filterContext.RequestContext);
var loginUrl = url.Content("/Default.aspx");
filterContext.HttpContext.Session.RemoveAll();
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.HttpContext.Response.Redirect(loginUrl, false);
filterContext.Result = new EmptyResult();
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Using Above custom attribute as follow in controller action:
[NoCache]
[CheckSessionExpire]
public ActionResult GetSomething()
{
}
AJAX Call(JS part):
function GetSomething()
{
$.ajax({
cache: false,
type: "GET",
async: true,
url: "/Customer/GetSomething",
success: function (data) {
},
error: function (xhr, ajaxOptions, thrownError) {
}
}
Web Config Authentication settings:
<authentication mode="Forms">
<forms loginUrl="default.aspx" protection="All" timeout="3000" slidingExpiration="true" />
</authentication>
I am try to check it by deleting browser cooking before making ajax call but event "CheckSessionExpireAttribute " does not get fired- any idea please.
Thanks,
#Paul
If I got the question right (and even if I didn't, thanks anyway, helped me solve my own situation), what you wanted to avoid was having your login page to load inside an element which was supposed to display a different View via Ajax. That or get an exception/error status code during a Ajax form post.
So, in short, the annotation class will need to override 2 methods, not just HandleUnauthorizedRequest, and it will redirect to a JsonResult Action that will generate the parameters for your Ajax function to know what to do.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class SessionTimeoutAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
IPrincipal user = filterContext.HttpContext.User;
base.OnAuthorization(filterContext);
if (!user.Identity.IsAuthenticated) {
HandleUnauthorizedRequest(filterContext);
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "AccountController", action = "Timeout" }));
}
}
}
Then set this annotation in your authentication Action, so every time it gets called, it will know where the request came from, and what kind of return it should give.
[AllowAnonymous]
[SessionTimeout]
public ActionResult Login() { }
Then your redirected Json Action:
[AllowAnonymous]
public JsonResult Timeout()
{
// For you to display an error message when the login page is loaded, in case you want it
TempData["hasError"] = true;
TempData["errorMessage"] = "Your session expired, please log-in again.";
return Json(new
{
#timeout = true,
url = Url.Content("~/AccountController/Login")
}, JsonRequestBehavior.AllowGet);
}
Then in your client function (I took the privilege of writing it as $.get() instead of $.ajax():
$(document).ready(function () {
$("[data-ajax-render-html]").each(function () {
var partial = $(this).attr("data-ajax-render-html");
var obj = $(this);
$.get(partial, function (data) {
if (data.timeout) {
window.location.href = data.url;
} else {
obj.replaceWith(data);
}
}).fail(function () {
obj.replaceWith("Error: It wasn't possible to load the element");
});
});
});
This function replaces the html tag with this data-ajax-render-html attribute, which contains the View address you want to load, but you can set it to be loaded inside the tag by changing replaceWith for the html() property.
I think that is only a client-side problem.
In web server you can just use the classic Authorize attribute over actions or controllers.
That will validate that the request is authenticated (if there's a valid authentication cookie or authorization header) and sets HTTP 401 if not authenticated.
Note: a session will automatically be recreated if you don't send authorization info in the request, but the request will not be authorized
Solution
Then the javascript client you must handle the redirect (browsers do it automatically but with ajax you need to do it manually)
$.ajax({
type: "GET",
url: "/Customer/GetSomething",
statusCode: {
401: function() {
// do redirect to your login page
window.location.href = '/default.aspx'
}
}
});
I checked and tested the code, looks like clearly.. the problem is that the ajax call is wrong..
I fix Ajax code, try this..
function GetSomething() {
$.ajax({
cache: false,
type: "GET",
async: true,
url: "/Customer/GetSomething",
success: function (data) {
},
error: function (xhr, ajaxOptions, thrownError) {
}
});
}
On HttpContext.Request.IsAjaxRequest()
Please see this related article on why an Ajax request might not be recognized as such.
XMLHttpRequest() not recognized as a IsAjaxRequest?
It looks like there is a dependency on a certain header value (X-Requested-With) being in the request in order for that function to return true.
You might want to capture and review your traffic and headers to the server to see if indeed the browser is properly sending this value.
But, are you even sure it's hitting that line of code? You might also want to debug with a break point and see what values are set.
On Session vs Authentication
Authorization and Session timeout are not always exactly the same. One could actually grant authorization for a period longer than the session, and if the session is missing, rebuild it, as long as they are already authorized. If you find there is something on the session that you'd be loosing that can't be rebuilt, then perhaps you should move it somewhere else, or additionally persist it somewhere else.
Form Authentication cookies default to timeout after 30 minutes. Session timeout default is 20 minutes.
Session timeout in ASP.NET
HandleUnauthorizedRequest not overriding
Sorry to say that: The solution you need is impossible. The reason is:
To redirect user to login page, we have 2 methods: redirect at server, redirect at client
In your case, you're using Ajax so we have only 1 method: redirect at client (reason is, basically, Ajax means send/retrieve data to/from server. So it's impossible to redirect at server)
Next, to redirect at client. Ajax need to information from server which say that "redirect user to login page" while global check session method must be return Redirect("url here").
clearly, global check session method can not return 2 type (return Redirect(), return Json,Xml,Object,or string)
After all, I suggest that:
Solution 1: Don't use ajax
Solution 2: You can use ajax, but check session timeout method at server which is not globally. Mean that you must multiple implement (number of ajax call = number of implement)

Google OAuth Issue

I have a Umbraco website that has google sign in button configured as follows:
At the top of the page (inside the header section) I have the scripts for calling google API:
<script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
<script>
function start() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init({
client_id: '<myapp client Id>.apps.googleusercontent.com',
// Scopes to request in addition to 'profile' and 'email'
redirect_uri: 'http://localhost:40136/umbraco/Surface/AuthSurface/GoogleAuthrizedUser',
scope: 'profile email'
});
});
}
</script>
In the body section of the code I have the google button setup and associated click function:
<script>
function onSignIn(authResult) {
if (authResult['code']) {
var authCode = authResult['code'];
console.log("Authorization Code: " + authCode);
$.post("/umbraco/Surface/AuthSurface/GoogleAuthrizedUser", { code: authCode })
.done(function(msg) {
// Success settings
})
.fail(function(xhr, status, error) {
});
} else {
//authResult['code'] is null
//handle the error message.
}
};
</script>
Controller code that handles the call back on the server end:
public class AuthSurfaceController : SurfaceController
{
public ActionResult GoogleAuthrizedUser()
{
string AuthCode = HttpContext.Request["code"];
var info = new GoogleAccessTokenResponse();
var client = new GoogleOAuthClient();
try
{
info = client.GetAccessTokenFromAuthorizationCode(AuthCode);
}
catch (Exception ex)
{
var strMessage = String.Format("<div class=\"info\"><p>{0}</p><p>{1}</p></div>", "Google Login Error",
ex.Message);
return Json(new AjaxOperationResponse(false, strMessage));
}
}
}
On the Serverside I am using Skybrud Social plugin for accessing google apis.
The google authentication happens in the popup and authorizes client with credentials and authResult['code'] has a valid code.
In the controller when I initialize the client and call the function GetAccessTokenFromAuthorizationCode(AuthCode), it returns an exception of 'Invalid Request'
I tried checking this authResult['code'] returned in the javascript function onSignIn in the https://developers.google.com/oauthplayground/
Same error description is shown 'Invalid request'. I am not sure why this is happening. The error returned is "invalid_grant"
Can anyone have a solution to this problem? What am I doing wrong here?
In your surface controller you're initializing a new instance of GoogleOAuthClient, but without setting any of the properties. The GetAccessTokenFromAuthorizationCode method requires the ClientId, ClientSecret and RedirectUri properties to have a value. You can initialize the properties like this:
// Initialize a new instance of the OAuth client
GoogleOAuthClient oauth = new GoogleOAuthClient {
ClientId = "The client ID of your project",
ClientSecret = "The client secret of your project",
RedirectUri = "The return URI (where users should be redirected after the login)"
};
You can read more about authentication in the documentation: http://social.skybrud.dk/google/authentication/ (the approach explained there will however not use any JavaScript)

how to send authenticated ajax call to web API

I have a Web Api Application which has the following question.
[HttpGet]
[Route("Account/userName{userName}/password={password}/rememberMe/{rememberMe}")]
public HttpResponseMessage LogIn(string userName, string password, bool rememberMe)
{
if (User.Identity.IsAuthenticated)
{
return Request.CreateResponse(HttpStatusCode.Conflict, "already logged in.");
}
var dbPerson = dbContext.Persons.Where(x => x.UserName.Equals(userName) && x.EncryptedPassword.Equals(password)).FirstOrDefault();
if (dbPerson != null)
{
FormsAuthentication.SetAuthCookie(userName, rememberMe);
return Request.CreateResponse(HttpStatusCode.OK, "logged in successfully");
}
else
{
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
}
I am calling from another MVC project. I Got the authentication but very next page where I am calling the ajax method
var uri = 'http://localhost:44297/api/XXXX';
$(document).ready(function () {
// Send an AJAX request
$.getJSON(uri)
.done(function (data) {
// On success, 'data' contains a list of products.
for (var i = 0; i < data.$values.length; i++)
{
}
})
.fail(function() {
console.log( "error" )});
});
I am getting GET http://localhost:44297/api/StudyFocus 401 (Unauthorized). how I can solve this issue. I know I need to pass some cookie/session value with this ajax call. but I don't know how. can anyone explain me with example.
My application relies on web Api project including authentication. I need to make web api application secure using form authentication. Any help is highly appreciable. Thanks
You can't authenticate web api by the use of cookies or session. You need access token to do that.
Follow this tutorial for the implementation http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api

Resources