Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Claims Based Forms Authentication Roles

I'm trying to authenticate a user using forms authentication in MVC 4 (I'm using RavenDB so I can't use the standard membership providers). Then later I'm using the User.IsInRole() method or AuthorizeAttribute to verify the user is in a staff role.

Here's where I set the ticket on successful authentication (at the moment in UserController.cs):

FormsAuthenticationTicket ticket =
    new FormsAuthenticationTicket(
        1,
        model.Email,
        DateTime.Now,
        DateTime.Now.AddDays(1),
        false,
        model.Email);

string hashedTicket = FormsAuthentication.Encrypt(ticket);

HttpCookie cookie =
    new HttpCookie(
        FormsAuthentication.FormsCookieName,
        hashedTicket);

HttpContext.Response.Cookies.Add(cookie);

Here's where I check the ticket for each request (Global.asax):

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];

    if (authCookie != null)
    {
        var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        var user = this.UserService.GetUserByEmail(authTicket.Name);

        var identity = new GenericIdentity(authTicket.Name, "Forms");

        var principal = new GenericPrincipal(identity, user.Roles);

        HttpContext.Current.User = principal;
    }
}

If I then put a debug point on one of my action methods (CalendarController.cs), I get isStaff equals false:

public ActionResult Index()
{
    var user = HttpContext.User;

    bool isStaff = user.IsInRole(Role.Staff);

    return View();
}

Just for completion (Roles.cs, just a temporary class to test things):

public static class Role
{
    public static string Staff
    {
        get { return "Staff"; }
    }

    public static string Manager
    {
        get { return "Manager"; }
    }
}

Can anyone help give me a point as to what I might be missing? It looks as though the roles I set are disappearing by the time I get to the action method.

like image 804
Adrian Thompson Phillips Avatar asked Jan 30 '13 13:01

Adrian Thompson Phillips


People also ask

How does role based authorization work?

Role-based authorization checks specify which roles which the current user must be a member of to access the requested resource. The controller SalaryController is only accessible by users who are members of the HRManager role or the Finance role.

How would you implement claims based authentication in .NET core?

The claims-based authorization works by checking if the user has a claim to access an URL. In ASP.NET Core we create policies to implement the Claims-Based Authorization. The policy defines what claims that user must process to satisfy the policy. We apply the policy on the Controller, action method, razor page, etc.

What is the use of forms authentication?

Forms authentication enables user and password validation for Web applications that do not require Windows authentication. With forms authentication, user information is stored in an external data source, such as a Membership database, or in the configuration file for an application.

Can you explain forms authentication in detail?

Form Authentication is a token-based system. When users log in, they receive a token with user information that is stored in an encrypted cookie. When a user requests an ASP.NET page via the browser, the ASP.NET verifies whether the form authentication token is available.


3 Answers

Thanks guys for helping me with this, what I've come up with (included below) works great! It auto-logs users straight in through the login screen if they have a valid ticket (cookie) and also handles Claims based roles using the ClaimsIdentity and ClaimsPrincipal objects, without putting the roles in the user's cookie. It also handles authentication in the Global.asax.cs file without having to resort to putting in custom authorize attributes.

UserController.cs

public ActionResult Login()
{
    LoginViewModel model = new LoginViewModel();

    if ((HttpContext.User != null) &&
        (HttpContext.User.Identity.IsAuthenticated))
    {
        return RedirectToAction("Index", "Home");
    }

    return View(model);
}

[HttpPost]
public ActionResult Login(LoginViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    bool isAuthenticated = this.userService.IsPasswordValid(model.Email, model.Password);

    if (!isAuthenticated)
    {
        ModelState.AddModelError("AuthError", Resources.User.Login.AuthError);

        return View(model);
    }

    FormsAuthentication.SetAuthCookie(model.Email, model.RememberUser);

    return RedirectToAction("Index", "Home");
}

Global.asax.cs

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];

    if (authCookie != null)
    {
        var ticket = FormsAuthentication.Decrypt(authCookie.Value);

        FormsIdentity formsIdentity = new FormsIdentity(ticket);

        ClaimsIdentity claimsIdentity = new ClaimsIdentity(formsIdentity);

        var user = this.UserService.GetUserByEmail(ticket.Name);

        foreach (var role in user.Roles)
        {
            claimsIdentity.AddClaim(
                new Claim(ClaimTypes.Role, role));
        }

        ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

        HttpContext.Current.User = claimsPrincipal;
    }
}
like image 180
Adrian Thompson Phillips Avatar answered Oct 19 '22 22:10

Adrian Thompson Phillips


Since you are using Raven I am assuming you made your own custom MembershipProvider and RoleProvider; and modified the web.config to use them. You should have an entry similar to this:

<membership defaultProvider="MyMembershipProvider">
  <providers>
    <add name="MyMembershipProvider" type="namespace.MyMembershipProvider, providerAssemblyName" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
  </providers>
</membership>
<roleManager enabled="true" defaultProvider="DefaultRoleProvider">
  <providers>
    <add connectionStringName="DefaultConnection" applicationName="/" name="DefaultRoleProvider" type="namespace.MyRoleProvider, providerAssemblyName" />
  </providers>
</roleManager>

If you are using .NET Framework version 4.5 it uses claims-based security and you do not need to store the roles in the cookie. Instead roles are just another claim that is stored in the ClaimsPrincipal. All principals now inherit from ClaimsPrincipal and it is stored for the users session in

System.Web.HttpContext.Current.User as ClaimsPrincipal

If your membership and role providers are setup correctly ASP.NET should use them to populate the roles in ClaimsPrincipal and then check the claims when you check IsInRole.

You can also retrieve roles from the ClaimsPrincipal for roles like this.

principal.FindAll(ClaimTypes.Role).Select(p => p.Value);

And you can add roles to a ClaimsPrincipal like this.

List<Claim> claims = new List<Claim>();
foreach (string role in roles)
   claims.Add(new Claim(ClaimTypes.Role, role));
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Forms"));

Now you can just set your cookie after authentication like this.

FormsAuthentication.SetAuthCookie(username, false);
like image 28
Kevin Junghans Avatar answered Oct 19 '22 23:10

Kevin Junghans


you are not creating the FormsAuthenticationTicket inserting roles info:

        var ticket = new FormsAuthenticationTicket(
                1, //ticket version
                userName,
                DateTime.Now,
                DateTime.Now.Add(timeout), //timeout
                true, //persistent cookies
                roles,// <---ROLES not model.Email
                FormsAuthentication.FormsCookiePath);

------EDIT-----

Forget what I said: i think you are calling IsInRole() too early or user.Roles has the wrong value (maybe spaces in strings: isinrole uses StringComparison.OrdinalIgnoreCase) or you should use FormsIdentity instead of GenericIdentity.

What the debugger says?

For reference: http://pastebin.com/jkqqcg28 (this is the starting model I use for handling authentication)

like image 32
giammin Avatar answered Oct 19 '22 22:10

giammin