When implementing an MVC web app with FormsAuthentication, it would appear that the FormsAuthentication mechanism automagically appends ?ReturnUrl=/ to your mvc routes. I did notice, though, that the default web application which MVC 3 provides out of the box implements FormsAuthentication but does not appear to suffer from the appended ReturnUrl=/ problem. I've looked through the code and can't see where this is being handled. Anyone know?
In the LogOn action of the AccountController, you'll see that if a return url is present, the user will be redirected to it after a successful log on:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Try adding the [Authorize] attribute to the Action or controller. This will generate the url needed to redirect after login.
Related
In my ASP.NET MVC 5 application, we have a bunch of user roles, such as Admin, ReportUser, ProjectManager and stuffs like these. I want the application to redirect the user to views immediately after login according to the following rules:
If user is Admin, redirect to /Admin/Index
If user is ReportUser, redirect to /Report/Index
If user is ProjectManager, redirect to /Project/Index
I've already set up a role manager using AspNetWindowsTokenRoleProvider in web.config, and I am setting user roles in MyMvcApplication_PostAuthenticateRequest of global.asax, which works well.
But, I am not clear where and what is the best way to do the redirect. In OnAuthorization method of a custom authorize attribute class? Or in MyMvcApplication_PostAuthenticateRequest method of global.asax?
If you're using the default Asp.net Identity Login and template logic you should have a login method that looks something like:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindAsync(model.Email, model.Password);
if (user != null)
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
So create a new method:
public async Task<ActionResult> GoHome()
{
if (this.User.IsInRole("Admin"))
{
return Redirect("Action", "Controller");
}
// etc etc
}
Then just update the Login method by changing:
return RedirectToLocal(returnUrl);
to
return Redirect("GoHome");
The reason for the double redirect is that authentication takes place before the MVC pipeline, which means that await SignInAsync(user, model.RememberMe) will not change the current user context, it requires a redirect for MVC to see the change. Thus on the next page, it can read the roles and redirect correctly.
I am having a problem with MVC4 user authorization.
System.Web.Security.Membership.ValidateUser returns true.
Then it gets to FormsAuthentication.SetAuthCookie and I see a cookie in my browser.
Then User.Identity.IsAuthenticated still evaluates to false for some reason.
User.Identity.IsAuthenticated
is still false after a redirect and stays false.
model.UserName
is not null or empty when I call SetAuthCookie. It has the correct username.
forms authentication is specified in web.config.
[AllowAnonymous]
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (System.Web.Security.Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
See also System.Threading.Thread.CurrentPrincipal
You should see
AuthenticationType "Forms"
IsAuthenticated true
Name "fred"
Did you tell MVC/IIS to authorize your controller ?
eg
[Authorize]
public class MyController : Controller
If User.Identity is not set, it would suggest WEB.CONFIG or an explicit AUTHORIZE attribute is missing.
I am using Forms Authentication in my MVC 3 app and having a problem with my return URL.
When I mark an action <Authorize> on the Home controller, it redirects to the login page and works, but the return URL is then /, so when it redirects, it is redirecting to the root of the the current URL Authorize.
So the URL's are like this:
http://localhost/ - Controller = Home - Action = Index
http://localhost/Authentication/LogOn
I end up with this: http://localhost/Authentication/LogOn?ReturnURL=~%2F, I need to get back to http://localhost/
Help!! :)
Try changing your Account controllers LogOn action to something like this:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
http://localhost/Authentication/LogOn?ReturnURL=~%2F,
it means home url is duplicated
I have two types of roles [Admin, HelpDeskAdmin].
I have a single logon view(both users go to same link to login) and I want to check their role once logged in and redirect to their respective admin pages once authenticated. The code below doesn't identify the logged in user as being in the role the first time and reloads the logon page, the second time they logon it redirects correctly.
Does this have something to do with the authentication cookie not being in place when it checks the first time? How could I accomplish this scenario?
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
if (Roles.IsUserInRole("Admin"))
{
//go to admin landing page
return RedirectToAction("Index", "Manage");
}
else if (Roles.IsUserInRole("HelpDesk"))
{
//go to helpdesk landing page
return RedirectToAction("Index", "Interview");
}
else /******FIRST TIME THROUGH IT ALWAYS GOES HERE *******/
return RedirectToAction("Index", "Home"); //not in any of those roles
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
The user is technically NOT logged in until the next request is handled and the Authenication Cookie is set....
Either do the redirection logic in another Action method or perhaps extract the user info using Roles.IsUserInRole(model.UserName, "Admin") [[Note specifying the username]] from within this Action.
i need a login widget thar appears on every page:
when user logs in he is redirected to the same page
if login fails user redirects to the same page and see error message
widget is rendered with Html.RenderAction
when submitting login form, i take current page url (with java script) and sent it to server - so I can redirect user to the same page after login
public ActionResult Login(string email, string password, string returnUrl)
{
if (userService.AuthenticateUser(email, password))
{
FormsAuthentication.SetAuthCookie(email, true);
if (!string.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Default");
}
else
{
ModelState.AddModelError("login_fail", "login failed");
if (!string.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
return View();
}
}
The problem is when errors happens: i need to show them to user but after redirect from Login action all ModelState data is lost (with errors).
The question is: how can I implement login widget to fulfill all above requirments?
Is AJAX an acceptable solution? This way you always stay on the same page and don't need to redirect. Another option would be to store the error message in TempData so that you can fetch it and show it on the redirected page:
public ActionResult Login(string email, string password, string returnUrl)
{
if (userService.AuthenticateUser(email, password))
{
FormsAuthentication.SetAuthCookie(email, true);
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Default");
}
if (!string.IsNullOrEmpty(returnUrl))
{
TempData["message"] = "login failed";
return Redirect(returnUrl);
}
ModelState.AddModelError("login_fail", "login failed");
return View();
}
Also for security reasons it is important to verify that returnUrl belongs to your domain before redirecting or a hacker could create a link and a returnUrl pointing to some spoofing site that looks exactly the same as your and which tricks the user into putting his username and password once again. The default ASP.NET MVC template in Visual Studio suffers from this same vulnerability.