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

23,424

Solution 1

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.

Solution 2

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);
            }
        }
    }
Share:
23,424
Mindless
Author by

Mindless

Just someone like you

Updated on July 09, 2022

Comments

  • Mindless
    Mindless almost 2 years

    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.

  • Mindless
    Mindless over 9 years
    Hi, Can you please explain why the else statement return RedirectToLocal(returnUrl); is needed?
  • Mindless
    Mindless over 9 years
    can i just do Redirect(returnUrl) and delete the RedirectToLocal action method?
  • tmg
    tmg over 9 years
    you need it cause of check if(Url.IsLocalUrl(returnUrl))
  • Kelum
    Kelum about 9 years
    @tmg I followed your approach and its very accurate and its worked for me , but when we put "[Authorize(Roles = "Employer")] " above the index action in " EmployerController " class and when we put "[Authorize(Roles = "Admin")] " above the index action in "AdminController" class this approach is not working , but without those authorizations, How can I restrict unauthorized access to these pages ?
  • tmg
    tmg about 9 years
    @kelum I have also decorated these controllers with Authorize attribute(to restrict access) and works. There must be something wrong elsewhere or I did not correctly understand you.
  • Kelum
    Kelum about 9 years
    previously I used webpages membership(SimpleMembership) system , since it doesn't have verification system I moved to AspNet Membership system , any conflict between those ? This is the error I'm getting when put authrizaion above that A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connectionsAll the connection are double checked all are fine .
  • tmg
    tmg about 9 years
    Please ask this as new question with details, its irrelevant to the subject of this thread.