Adding custom roles to windows roles in ASP.NET using claims
If you’ve been using WIF (Windows Identity Foundation) for any amount of time this shouldn’t be anything new, but for folks that haven’t had their eyes opened yet to using claims-based identity then I wanted to show how it’s very easy to add custom roles to windows roles (or any other claim type for that matter).
Here’s the requirement: I’m using windows authentication and I get all the groups back for the user as roles, but I want to also add additional application specific roles to the user for authorization purposes.
First thing to note here is that if you’re using windows authentication then you don’t need to use the WindowsTokenRoleProvider since the user’s groups are already loaded via windows authentication and most of the methods in this class throw an exception letting you know they’re not implemented (thus illustrating that role providers aren’t all that useful).
Second, if you’re using .NET 4.5 (since all the identity classes are claims-aware) then it’s dirt simple to augment them with custom claims (including roles). In ASP.NET you’d need to hook the same event in the HTTP pipeline that you’d hook for custom roles (as I already pointed out here). In short you need to load your custom roles (or claims) from your custom store/database and then augment the current principal with them in the Application_PostAuthenticateRequest in global.asax. Here’s the code:
void Application_PostAuthenticateRequest() { if (Request.IsAuthenticated) { string[] roles = GetRolesForUser(User.Identity.Name); var id = ClaimsPrincipal.Current.Identities.First(); foreach (var role in roles) { id.Claims.Add(new Claim(ClaimTypes.Role, role)); } } }
HTH
This is very interesting to me. So can I assume using this method it is more responsive when I want to use the Authorize Attribute on MVC Controllers or individual methods inside of a specific controller? I felt forced to hack create my own Authorize attribute because of the MVC limitations. I think a bit of a hack, but I really needed to define my application roles that would be assigned AD Groups of people. http://thomasgathings.com/?p=34
If what you have above allows me to use the native Authorize Attribute with a string of roles that are application defined I may dance a jig.
These are doing different things — the PostAuthRequest allows for a place to load additional info about user. Prior to claims this was done as roles, but now with claims you can load any data.
The [Authorize] attribute is doing authorization checks. If you built custom attributes to do DB checks, they could instead do checks against the claims that were loaded from the PostAuthRequest.
What would be the best way to add a custom role (or other custom claim) to a security token, in a way that this claim would exist in the session cookie for subsequent requests, and even make the bootstrap token to contain the custom claim, to allow delegating the token to a web API service?
You’d have to issue the token yourself like this: https://brockallen.com/2013/01/26/replacing-forms-authentication-with-wifs-session-authentication-module-sam-to-enable-claims-aware-identity/
Oh sorry, I missed the last part of your question(s) about delegating. As for delegation, it depends on what the backend that you’re calling into is and how it’s expecting the token. If it’s a SOAP API, then look into delegation with WS-Trust. If the backend is a HTTP-based api then you’d need some sort of OAuth2-like authorization. I guess I’m not going to have an answer for you since it very much depends on many factors.
Brock,
I have recently tried to implement this, with just a slight change necessary due to language updates I assume.
I Changed
id.Claims.Add(new Claim(ClaimTypes.Role, role));
To
id.AddClaim(new Claim(ClaimTypes.Role, role));
The code seems to work up until that part. The problem is Whenever I run ClaimsPrincipal.Current.IsInRole(role) it always returns false. I have iterated through the claims collection for the roles and they appear :
{http://schemas.microsoft.com/ws/2008/06/identity/claims/role: Author}
Issuer: “LOCAL AUTHORITY”
OriginalIssuer: “LOCAL AUTHORITY”
Properties: Count = 0
Subject: {System.Security.Principal.WindowsIdentity}
Type: “http://schemas.microsoft.com/ws/2008/06/identity/claims/role”
Value: “Author”
ValueType: “http://www.w3.org/2001/XMLSchema#string”
But the IsInRole method never returns true.
I don’t know if this is an old deprecated method, a new issue caused by a newer version of the development environment (VS2013 premium update 1) or an isolated incident. If bad comes to worse I guess I’m going to have to override the IsInRole method.
You shouldn’t have to override IsInRole. I’ll have to check this when I get a chance.
Brock,
Right now I am getting things to work by using a new generic principal and using the windows principal identity in the constructor as well as the roles obtained from the db. I then assign the principal to the thread and my methods can verify based on those custom roles.
This is due to the claims principal and how the IsInRole method varies between a “ClaimsPrincipal” and the “WindowsPrincipal”. What is means is using windows authentication the “IsInRole” method is checking against windows user groups, the domain not the role claims in the token.
Therefore when you do something like this.User.IsInRole(“Admin”) it will never find it and return false. In order to return true you need to have a windows user group set on the local computer or domain and the user who is logged in needs to be part of it and call it like this.User.IsInRole(“DOMAIN\Admin”)
See http://msdn.microsoft.com/en-us/library/system.security.principal.windowsprincipal.isinrole(v=vs.110).aspx for some more information.
ClaimsPrincipal.Current.IsInRole(role) is always returning false. Using ClaimsPrincipal.Current.HasClaim(ClaimTypes.Role, role) instead.
The behavior of IsInRole depends on the RoleClaimType passed to the ClaimsPrincipal ctor.
This is a pretty valuable post to me. It seems quite straightforward, but I am having trouble getting things working properly.
I modified your code slightly in the Application_PostAuthenticateRequest() to:
var id = ClaimsPrincipal.Current.Identities.First();
foreach (var role in roles)
{
id.AddClaim(new Claim(ClaimTypes.Role, role));
}
My goal, if I have a role “Admin” in roles, I could just decorate a controller class with this: [Authorize(Roles = “Admin”)].
Am I perhaps misunderstanding the point of this article?
Thanks for your help!
Should work — what’s the error/problem?
Do we have to load the roles for the user on each request. I understand Session doesn’t work in this event. Also on next request the roles has to be reloaded. What if we are fetching roles from DB? Each Ajax calls will hav a DB call for Role.
Cache the roles in-memory then if you think there are too many look ups in the DB.
Went down this road for a new MVC 5 application, but the MSDN documentation states that “Starting with the .NET Framework 4.5, Windows Identity Foundation (WIF) has been fully integrated into the .NET Framework. The version of WIF addressed by this topic, WIF 3.5, is deprecated and should only be used when developing against the .NET Framework 3.5 SP1 or the .NET Framework 4…”. Is there another way I should be looking at doing this now?
never mind, I think I was just looking at Microsoft.IdentityModel.Claims and not System.Security.Claims documentation. Thanks and sorry.
Hi Brock – Thanks for all your articles on this rather confusing subject, I’ve read several of them. If you have a moment, I hope you can answer my question. I have a situation where my project (intranet app) wants to use Windows authentication, but all the role-based stuff is already in a database and there are about 6,000+ employees. I went ahead and implemented and cached (as the last question suggests) the role information. Problem is User.IsInRole and Authorize were not working. So then I went ahead and created my own Custom AuthorizeAttribute and my own “IsInRole”. I didn’t like that solution because if I want to protect a static resource, I can’t do it just by a web.config entry. So now I have implemented my own RoleProvider instead which seems like it is the “old” way of doing things. I still use and cache claims, but once I added the RoleManager node to web.config this line stopped working:
var id = ClaimsPrincipal.Current.Identities.First();
So now I just query the cached data (which is a list of Claims) inside the RoleProvider. It all works just fine, but is this the wrong path and is there a better way to integrate Windows Authentication with custom Roles from the database and still have the Authorize and IsInRole stuff work automatically?
Thanks for your time!!
Brian Edwards
You might need to replace the entire ClaimsIdentity and set the name claim type and role claim type on the ctor.
You saved my life with this post; I should spend more time getting to know System.Web.HttpApplication… Thanks!
Great Post Brockallen, however like some others here the User.IsInRole(“”) and Authorize Attributes on MVC Controllers e.g. [Authorize(Roles = “Admin”)] don’t appear to be working.
Is it possible to use Windows Authenication and “Claims Roles” for this?
You’d have to check what the claims are in the user’s claims collection. Also, when the ClaimsIdentity is created that indicates which claim type is consulted for IsInRole checks — you can change this, of course.
For IsInRole() to work, you can add your custom roles like this:
identity.AddClaim(new Claim(ClaimTypes.GroupSid, “SomeRoleName”));
The key here is choosing the correct ClaimType type (i.e. GroupSid)
Amazing – thanks. I’ve spent hours trying to find the answer to this. ClaimTypes.GroupSid did the trick for me.
YESSSS!!!! FINALLY!! ClaimTypes.GroupSid was the answer!