asp.net mvc: developing login widget - asp.net-mvc

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.

Related

What's the best way to direct user to different views by roles?

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.

How to get requested Url before Login

Some Controller-Actions in my page are decorated with the [Authorize] attribute. The forward to the Login page works perfect, but after successful login, I want to forward to the requested url. With Request.UrlReferrer, I get the url where I come from, but how can I get the requested action (which requires the login)?
The Login() action takes a string parameter called returnUrl which will be used when redirecting from the login.
You can add the returnUrl to the ViewBag and return a view.
For Eg:
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
The target action uses the posted data to authenticate the user and log in the user using FormsAuthentication. It then redirects the user back to the returnUrl.
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (//Validation Check)
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return Redirect(returnUrl); // Redirect to referer
}
ViewBag.ReturnUrl = returnUrl;
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}

ASP.NET MVC 3 FormsAuthentication ReturnURL

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

Default MVC web app has no ReturnUrl

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.

ASP.net MVC Membership Redirect Depending on Role

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.

Resources