Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC 5 Identity 2 Login redirect based on user role

I am trying to redirect user to page based on their role,

This is the default implementation of the login function that come with ASP.NET MVC 5:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, 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);
}

private ActionResult RedirectToLocal(string returnUrl)
{
    if (Url.IsLocalUrl(returnUrl))
    {
        return Redirect(returnUrl);
    }
    else
    {
        return RedirectToAction("Index", "Employer");
    }
}

I want to be able to redirect user based on their role, I tried to do it this way:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);
        if (user != null)
        {
            await SignInAsync(user, model.RememberMe);

            //role Employer go to Employer page
            if (UserManager.IsInRole(user.Id, "Employer"))
            {
                return RedirectToAction("Index", "Employer");
            }
            //role Admin go to Admin page
            else if (UserManager.IsInRole(user.Id, "Admin"))
            {
                return RedirectToAction("Index", "Admin");
            }
            else
            {
                //no role
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.");
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

But there is a problem, although the site is redirecting me to the correct page, If i navigate by typing in the url foo.com/admin when i am not logged in with an admin account, the site brings me to the login page with url foo.com/Account/Login?ReturnUrl=%2Fadmin, which is the expected behavior.

if i login with an employer account at this point, it will redirect me to the employer's page and log me in as an employer, which isn't wrong, but that shouldn't be the case, the site should mention I should login with an admin account instead because the return url is "admin". I hope i am making sense.

like image 516
Mindless Avatar asked Oct 23 '14 10:10

Mindless


2 Answers

why dont you check if there is a returnUrl before your custom redirects?

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
         if (ModelState.IsValid)
         {
              var user = await UserManager.FindAsync(model.UserName, model.Password);
              if (user != null)
              {
                    await SignInAsync(user, model.RememberMe);
                    if (String.IsNullOrEmpty(returnUrl))
                    {
                         if (UserManager.IsInRole(user.Id, "Employer"))
                         {
                             return RedirectToAction("Index", "Employer");
                         }
                         //role Admin go to Admin page
                         if (UserManager.IsInRole(user.Id, "Admin"))
                         {
                             return RedirectToAction("Index", "Admin");
                         }
                     }
                     else
                     {
                         return RedirectToLocal(returnUrl);
                     }
              }
              else
              {
                     ModelState.AddModelError("", "Invalid username or password.");
              }
       }

       // If we got this far, something failed, redisplay form
       return View(model);
   }

Like this if you navigate to foo.com/admin it will throw 401 and redirect you to login. Then if you log in as employer it will throw 401 and redirect you back to login again.

From comments: "Can I just do Redirect(returnUrl) and delete the RedirectToLocal action method?"

RedirectToLocal(returnUrl) method checks if Url.IsLocalUrl(returnUrl). So it is needed to prevent Open Redirect Attacks.

like image 51
tmg Avatar answered Nov 12 '22 21:11

tmg


Despite [ this ] article being written in 2008, it helped me with the solution to this problem. It gives all the code samples you need to redirect users on login without cluttering up your Login method. If you add a new role and want to redirect users with that role, it's as simple as adding a line in web.config.

I did come across one gotcha. In his article he has the following code which you would put in your AccountController (or wherever you want to perform the redirect):

/// <summary>
/// Redirect the user to a specific URL, as specified in the web.config, depending on their role.
/// If a user belongs to multiple roles, the first matching role in the web.config is used.
/// Prioritize the role list by listing higher-level roles at the top.
/// </summary>
/// <param name="username">Username to check the roles for</param>
private void RedirectLogin(string username)
{
    LoginRedirectByRoleSection roleRedirectSection = (LoginRedirectByRoleSection)ConfigurationManager.GetSection("loginRedirectByRole");
    foreach (RoleRedirect roleRedirect in roleRedirectSection.RoleRedirects)
    {
        if (Roles.IsUserInRole(username, roleRedirect.Role))
        {
            Response.Redirect(roleRedirect.Url);
        }
    }
}

My application was failing to find a role provider, and when I added one in web.config, I had a difficult time getting it to find my roles. I decided to ditch the role provider and use the UserManager to get the user and the roles:

    /// <summary>
    /// Redirect the user to a specific URL, as specified in the web.config, depending on their role.
    /// If a user belongs to multiple roles, the first matching role in the web.config is used.
    /// Prioritize the role list by listing higher-level roles at the top.
    /// </summary>
    /// <param name="username">Username to check the roles for</param>
    private void RedirectLogin(string username)
    {
        LoginRedirectByRoleSection roleRedirectSection = (LoginRedirectByRoleSection)ConfigurationManager.GetSection("loginRedirectByRole");
        var user = UserManager.FindByName(username);
        var rolesForUser = UserManager.GetRoles(user.Id);
        foreach (RoleRedirect roleRedirect in roleRedirectSection.RoleRedirects)
        {
            if (rolesForUser.Contains(roleRedirect.Role))
            {
                Response.Redirect(roleRedirect.Url);
            }
        }
    }
like image 26
Daryl Avatar answered Nov 12 '22 20:11

Daryl