Custom Claims-based Windows Authentication in ASP.net MVC 5
When I first read about the Claims-based identity/authorization, I immediately liked the idea. Claims are simple key-value pairs, dictionary type of objects and make it very easy for the developers to implement authorizations. You can very easily implement custom authorization class in your MVC project to use these claims and decorate your controllers with this custom Auth filter passing the claims key-values that you want to check against to grant user access to the controller. You can even use these claims in your views , e.g to show a delete button to a user with claims value for the key "role" = admin.
Let's look at how to implement custom claims on top of Windows Authentication in ASP.NET MVC.
( Below I am narrating a little about how I failed implementing Claims-based Windows authentication using OWIN and Directory services together. Feel free to skip this greyed section and directly jump to the crux of the topic - how I implemented Custom Claims-based Windows Authentication. ) After I read about the claims-based identity, I regretted that I was not aware of it when couple of years ago, in an earlier Webforms based .net project, I had implemented a custom forms-based authentication. There the requirement was to allow users to authenticate using their Quality-Center credentials to log into a particular QC domain/project. For this, I used QC API that provides functions to authenticate user. On top of QC project, the application internally needed users to register in custom Group-Team combinations. This forms-based QC authentication was implemented using a custom MembershipProvider class inherited from System.Web.Security.MembershipProvider, and a custom FormsAuthentication ticket, to generate user identity on login. I added couple of classes to store user's authorizations. After user is authenticated, I would instantiate authorization classes, initialize those using user-related data read from DB and then storing these authz objects in a persistent session HttpCookie that will be used in subsequent requests from the user. Although this works absolutely fine, had I knew about claims, I would have saved myself some time developing the custom authorization classes for my application. So, few months back, when we started on a new application, I had no doubts about using claims-based identity to use claims for authorizations. This would be an intranet application. For this application, the requirement was to authenticate users using Windows Authentication with no login form involved. And then the application would need some custom authorizations based on user settings to be read from an existing database. I planned to use MVC 5 for UI as well for the EntityFramework-based WebAPI for database communication. When I was starting on this new application, around the same time, I happened to attend Rachel Apple's presentation on OWIN/Websockets and I was thrilled with the idea. Further, I noticed that when creating MVC 5 project from Visual Studio 2013, it sets up the basic classes/entities/interfaces for you to implement OWIN middleware that would be hosted in IIS for your web application to provide the required Identity 2 based authentication. However I was not sure if OWIN would be much required when using only Windows authentication. I also noticed that if you select 'Windows Authentication' option at the time of creation of MVC project, then VS doesn't create the OWIN configuration for the project. This was an indication that OWIN based Identity 2 would not fit good for the Windows Authentication. There are workarounds though and you can always extend/customize the required classes as per your needs. Here are two excellent articles to get you started with ASP.net Identity 2: Understanding-OWINKatana-AuthenticationAuthorization
Also to note, if you are implementing Forms-based Authentication/Authorization from scratch, the Visual Studio will also set up the identity using pre-defined entity models of users and roles, and would also help you create tables for these user and role entities in DB using 'Code-first EntityFramework'. This would be very helpful if you are building your auth/identity module from scratch, but this would be very frustrating if you want to use some pre-existing database's user/roles tables, which was my case. I was very much tempted to implement this OWIN-Identity2 based authentication/authorization for my new MVC application. Like said earlier, the requirement was to automatically authenticate user using Windows Authentication and then add the authorizations to the user identity using the user's roles and authorizations from DB. Because, the default user/role models that Visual Studio MVC5 project sets for you, were not useful for me, I started with customizing these entity classes as per my requirements, based on existing user, role and authorization related DB tables we had in our existing DB. After few trial-and-errors, I was able to successfully implement the OWIN-based basic Windows authentication and could also add some custom claims to the identity. However then came the tricky part - using Directory services. Our user-related tables in DB didn't store all the information of the windows users. Like it stored the user name, but didn't store the Employee ID. However we needed employee ID to call another service that would give user's picture using employeeID. When I started adding directory services classes, I could add claims using the properties of the Principal that Directory Services would return. However when I hosted my application in IIS, I had trouble. The combination of OWIN and Directory services won't work. OWIN needs your app pool to use Integrated mode, whereas Directory services won't work if the App Pool is set for Integrated mode, it works in classic mode of App pool. Although using some registry settings, you can make the directory services work in Integrated mode, however still I faced few other issues with this approach of OWIN and Windows Authentication along with Directory services. Time was running out, so finally I decided to give up on OWIN and just build simple claims-based identity using Windows Authentication and directory services. This was very easy to set. In the next section, lets look at how to implemented this Windows Authentication with Claims-based identity Authorization. |
Setting up project and required classes
Creating project
1. Create a new MVC 5 project in Visual Studio.
2. Once the project gets created and is shown in the solution explorer, from its properties window, set Windows Authentication = Enabled
. You can keep Anonymous Authentication=Enabled
too in case you think Windows Authentication is not needed for some controllers/view, those could be accessible to any anonymous user.
Required project references
Add references to System.IdentityModel and System.IdentityModel.Services in the project references.
Web.config settings
Now lets first set up the needed configuration in Web.config.
In order to save the user's identity in session cookie, we need to add below under <configSection>
element in web.config:
To allow authentication to be saved in session, we also need to add a SessionAuthenticationModule
under system.web/modules node.
And because we don't need any forms based authentication, we will remove FormsAuthenticationModule
from here, as below:
Under system.web element, set authentication
mode="Windows":
The session cookie by default forces application to require SSL. This can be disabled in case of intranet application by setting requireSSL attribute of cookieHandler
to 'false'. Add below section under <configuration>
node in web.config
And lastly, we need to register the module in our application that will handle the claims to be added to user's identity. Under <configuration>
node, add the node <system.identityModel>
somewhere, as shown below:
AppClaimsManager.cs
This is the class where we will set the custom claims that we need to add to user's Identity, and then write it to a session cookie token.
We inherit this class from System.Security.Claims.ClaimsAuthenticationManager
. Make sure you have referenced - System.IdentityModel and System.IdentityModel.Services namespaces to the project.
The overridden Authenticate method, takes two parameters -
resourceName - The string describing the resource that is being requested. |
incomingPrincipal - The claims principal that represents the authenticated user that is attempting to access the resource. |
In this overridden Authenticate function, we are first checking if the incomingPrincipal is an authenticated user by checking incomingPrincipal.Identity.IsAuthenticated
property. If user is not authenticated, we simply call the base class Authenticate
method to handle user's authentication.
If the user is authenticated, we proceed to build our custom claims principal.
Because we have Windows Authenticated enabled for the project, the second parameter - incomingPrincipal
- will be the Windows authenticated UserPrincipal, which in-fact already is a claims-aware identity. But the claims it carries are mostly the AD groups to which user belongs. Because we need some additional information, like email or Employee IDs, about the user, we call a private function AddCustomClaimsToPrincipal passing it userPrincipal's username as parameter; where we build the custom claims needed for our user.
In AddCustomClaimsToPrincipal
function, we use System.DirectoryServices to get the UserPrincipal
using the username
we got from user's WindowsPrincipal. We then create a new claims list customClaims, to which we add the custom claims that we want to get from user's AD identity. Here, additionally we can also read some user related data from DB and add to this claims list. A new claims Identity - theCustomClaimsIdentity - is then created using this custom claims list. Finally (line # 49-50) we return back a new Principal created using this theCustomClaimsIdentity.
After this, this returned ClaimsPrincipal , with custom claims identity, is saved to a session security token, by passing it to another private function CreateSessionSecurityToken. (line # 23, 56-59).
Finally, we again return back the value of incomingPrincipal.Identity.IsAuthenticated
property, which would be true this time.
AppClaimsAuthorizeAttribute.cs
Here in this class, we would customize the Authorize attribute, inheriting it from System.Web.MVC.AuthorizeAttribute
to override its AuthorizeCore
method. The AuthorizeCore method is the entry point for any authorization checks. When a controller, or any action method, is decorated with this filter.
AuthorizeCore
method is called to check if the user meets the authorization requirements.
In the previous class AppClaimsManager.cs
, we added custom claims to user's claimsIdentity. Now we'll use these claims to authorize a user based on the value of particular claim type that user may have in its claimsIdentity.
For e.g we can check if the value of claim type myAppUserRole is myAppAdmin in user's claimsIdentity, then authorize the user to Product controller by decorating the controller like below:
[AppClaimsAuthorize(ClaimType = "myAppUserRole", ClaimValue = "myAppAdmin")]
public class ProductController : Controller
{
....
}
As we see, the AppClaimsAuthorize filter in the above code needs two parameters to authorize against user's claims identity - ClaimType and ClaimValue.
For this, in our overridden AppClaimsAuthorizeAttribute , we will declare these two public properties - ClaimType and ClaimValue.
Now, in the overriden AuthorizeCore
method, we first check if the user is authenticated (context.Request.IsAuthenticated
) and also check if the base class base.AuthorizeCore(context)
returns true.
Once these two checks are passed, we now need to validate the passed ClaimType and ClaimValue against the user's claims Identity. For this we will need to read the user's claimsIdentity from the stored session security token for the user by checking if the context.Request.cookies
contains the session security token cookie.
For the first request sent from user's client side for a resource of the application, this security token cookie will not be available in the user session. So, for this first request, we will use the AppClaimsManager class, that we went thru in the previous section, to set up user's custom claims identity and save it as session security token cookie, to be used in subsequent requests from the user.
Thus, we are getting the user's claimsIdentity , either by creating it using AppClaimsManager class for the first time, or later, by simply reading it from the user's session security token cookie, in subsequent requests from the user.
Once the user's claimsIdentity is available to us, we can check for the passed ClaimType and ClaimValue in this claimsIdentity. This check we implement in a private function - checkClaimValidity
. If the passed Claimtype-ClaimValue pair is found in the user's claims identity, then the user is authorized to access the resource, else the user is unauthorized for the resource.
Below is the code for AppClaimsAuthorizeAttribute.cs
:
☝️, we override the HandleUnauthorizedRequest
method(line # 22). If the method - AuthroizeCore
- returns false, then the flow will shift this method where any unauthorized request for a user will be handled. In our implementation of this method, we direct the user to an "Error" view (line # 34).
And if the user is not authenticate at all, we direct the user to home controller(line # 25-30).
Disable OWIN
Because we are not going to use OWIN for authentication, we disable it by commenting the call to ConfigureAuth
in start.cs as below. ( Note that, you may not have this file if you selected 'Windows' or 'No Authenitcation' option for Authentication when creating the project. If this is the case, you may skip this step. )
Using Claims in Controllers
In the above section, we've set up the classes and web.config that would enable us to use custom claims-based windows identity. We are now ready to use these custom claims in our controllers or views.
If you want a controller to be accessible to a user that has a value of "myAppAdmin" for claim-type "myAppUserRole" , we simply decorate the controller with our custom Authorize attribute AppClaimsAuthorizeAttribute with the two required properties - ClaimType and ClaimValue set to "myAppUserRole" and "myAppAdmin" respectively as shown in below code:
Below is the controller code decorated with AppClaimsAuthorizeAttribute annotation.
(Note - when decorating the controller, the word Attribute is removed from the class name AppClaimsAuthorizeAttribute, as per MVC framework standards.)
Using Claims in Views
We can also use the custom claims that we added to user Principal's identity in our MVC views.
To use the claims in views, first create a class - AppUserPrincipalClaims - to the project. This class is inherited from System.Security.Claims.ClaimsPrincipal
.
In this class, we will define a read-only property for each of the custom claims the we set up for the user in AppClaimsManager.AddCustomClaimsToPrincipal
. The get
method of these read-only properties simply returns the value of the corresponding ClaimType using FindFirst
method of the base ClaimsPrincipal class.
Below is the code for AppUserPrincipalClaims.cs
:
Next, we create an abstract class - AppCommonViewUtils<TModel> - inherited from WebViewPage<TModel>
. In this class we will instantiate the above created AppUserPrincipalClaims class in a read-only property appUserClaims.
And next we again define abstract class AppCommonViewUtils inherited from AppCommonViewUtils<TModel>, with <TModel> specified as <dynamic> so that we can base any dynamic view on this class.
Below is the code for AppCommonViewUtils<TModel>
andAppCommonViewUtils
classes:
Because, AppCommonViewUtils class is derived from WebViewPage<TModel>, we can specify it as the base class for all our views in the web.config
file which is under Views folder, as shown below:
views/web.config
And that's it. Now that all our views are going to be based on AppCommonViewUtils class, which gives us a public property appUserClaims to return our AppUserPrincipalClaims, we can now use our custom claims in our views by simply accessing this property with razor syntax e.g. - @appUserClaims.GivenName would return the value of GivenName
claim-type from user's claim's identity, as shown in the below code.
☝️ Above is the default _loginPartial.cshtml file that gets build when we create MVC project in Visual Studio 2013, under 'views/shared' folder. In this I replaced the below code at line# 10 -
with
to use our custom claims Identity.
References
Introduction to Claims based security in .NET4.5
Andras Nemes - Claims-based authentication in MVC4 with .NET4.5 C#
Ben Foster - ASP.NET Identity Stripped Bare