56

I am upgrading a site to use MVC and I am looking for the best way to set up Authentication.

At this point, I have the log-in working off of Active Directory: validating a username and password, and then setting the Auth cookie.

How do I store the user's role information at time of log-in, in order for my controllers to see those roles as the user navigates through the site?

[Authorize(Roles = "admin")]

I have no problem getting a list of roles from Active Directory. I just don't know where to put them so that the controllers will see them.

alex
  • 6,818
  • 9
  • 52
  • 103
Billy Logan
  • 2,833
  • 16
  • 43
  • 49

5 Answers5

127

Roles are added to the IPrincipal of the HttpContext. You can create a GenericPrincipal, parse the list of roles in the constructor and set it as HttpContext.User. The GenericPrincipal will then be accessible through User.IsInRole("role") or the [Authorize(Roles="role")] attribute

One way of doing this (in C#) is to add your roles as a comma separated string in the user data parameter when creating your authentication ticket

string roles = "Admin,Member";
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
  1,
  userId,  //user id
  DateTime.Now,
  DateTime.Now.AddMinutes(20),  // expiry
  false,  //do not remember
  roles, 
  "/");
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
                                   FormsAuthentication.Encrypt(authTicket));
Response.Cookies.Add(cookie);

Then access the role list from the authentication ticket and create a GenericPrincipal from your Global.asax.cs

protected void Application_AuthenticateRequest(Object sender, EventArgs e) {
  HttpCookie authCookie = 
                Context.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie != null) {
      FormsAuthenticationTicket authTicket = 
                                  FormsAuthentication.Decrypt(authCookie.Value);
      string[] roles = authTicket.UserData.Split(new Char[] { ',' });
      GenericPrincipal userPrincipal =
                       new GenericPrincipal(new GenericIdentity(authTicket.Name),roles);
      Context.User = userPrincipal;
    }
  }
Community
  • 1
  • 1
David Glenn
  • 24,412
  • 19
  • 74
  • 94
  • 1
    If Klaus hadn't answered well before you i would have given you the correct answer. This is the exact way i am doing things as well. Thanks for the detailed answer. Sorry it was a little to late. – Billy Logan Dec 01 '09 at 15:04
  • I guess this is not going to work for users that have cookies disabled ? – Omu Feb 17 '10 at 20:16
  • 1
    @Omu You can support cookieless authentication by simply modifying the Application_AuthenticationRequest() method to look for the authentication ticket in the URL if the cookie is not present. The important part is that the roles are stored in the encrypted authentication ticket. – David Glenn Feb 17 '10 at 22:27
  • 5
    If you store roles in a cookie like this, what happens if a user's role is removed by an administrator or something? You wouldn't see this update until the cookie expires or you clear the cookie cache? – Joshua Hayes Nov 22 '10 at 22:39
  • this is a great answer, and I've tried to use it, but it's not working for me. Could you please see my question; maybe it'll be obvious to you what I'm doing wrong? http://stackoverflow.com/questions/5314673/mvc-authentication-roles-not-working – Shaul Behr Mar 15 '11 at 16:20
  • 2
    This event is invoked for each request (including images, css, etc). Would you want to put in a condition that only adds roles once? – Scott Coates Mar 02 '12 at 08:09
  • @DavidGlenn do I have to change FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); to FormsAuthenticationTicket stuff? Thanks – VAAA Jul 24 '12 at 19:34
  • 7
    I've tested it in ASP.NET MVC 4 and I suggest to use `Application_PostAuthenticateRequest` instead. Otherwise the generic principal will be overridden. – jwaliszko Sep 07 '13 at 16:18
  • I always set Thread.CurrentPrincipal=userPrincipal with Context.User=UserPrincipal; otherwise User is null in some cases. – Lev Aug 27 '14 at 10:07
  • 1
    This is a OK approach, but why wouldn't you use the stored user's Id in authticket.Name to make a call to the database within the Application_PostAuthenticateRequest method to retrieve their roles and initiate the IPrincipal that way? I know it's two calls to the DB but it saves storing them in a cookie and prevents hacks. – Kevin Dark Sep 06 '16 at 20:23
6

When you authenticate your user, you generate a new GenericPrincipal instance. The constructor takes an array of strings which are the roles for the user. Now set HttpContext.Current.User equal to the generic principal and write the auth cookie, and that should do it.

Kiquenet
  • 14,494
  • 35
  • 148
  • 243
Klaus Byskov Pedersen
  • 117,245
  • 29
  • 183
  • 222
5

For those of you using MVC 4 or Greater you will need to take Jaroslaw Waliszko's advice when making use of David Glenn's answer:

"I've tested it in ASP.NET MVC 4 and I suggest to use Application_PostAuthenticateRequest instead. Otherwise the generic principal will be overridden." – Jaroslaw Waliszko Sep 7 at 16:18

So as stated above, all you need to do is replace the Application_AuthenticateRequest method name with Application_PostAuthenticateRequest to get this to work. Worked like a charm for me! If I was allowed to upvote Jaroslaw and David, I would.

Gareth
  • 243
  • 1
  • 5
  • 12
  • 3
    I tried but my Stack Overflow reputation is non-existent due to me being a newly registered member so I can't comment. If this goes through it would shock and surprise me :) *Queue shock and surprise* – Gareth Jan 06 '14 at 05:52
3

I'd be inclined to just create a custom role provider. Example here:

http://www.danharman.net/2011/06/23/asp-net-mvc-3-custom-membership-provider-with-repository-injection/

DanH
  • 3,772
  • 2
  • 27
  • 31
1

Could you not drop in either an authorization store role manager or find (e.g. on Codeplex) or write another Role Provider that works with Active Directory to get the groups information?

This would save you the hassle of authenticating the user, getting their roles, and then re-passing that information into the constructor, and would all happen automatically for you as part of the framework.

Zhaph - Ben Duguid
  • 26,785
  • 5
  • 80
  • 117