ASP.NET Identity for your custom user and roles models
I’ve been seeing a lot of requests by developers in the past months who seem to be struggling with adding their custom user authentication model into their application; they want to be able to integrate it into the ASP.NET pipeline to play nice with the Authentication middleware (i.e. AuthorizeAttribute). ASP.NET Identity Core has been rewritten to leverage the use of interfaces (abstraction!) so you can easily develop a system that caters to your needs.
In this post, I want to show you how to leverage that by using ASP.NET Identity in an ASP.NET Core 2.0 application.
Setup
The first thing is to add the ASP.NET Identity package to your project. In Visual Studio 2017, you can right click on the Dependencies node in the project and click Manage NuGet Packages. Search and install Microsoft.AspNetCore.Identity. You can also edit the csproj and add the following line in the ItemGroup node. Note that at the time of writing the latest version of this package is 2.0.1.
1 |
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.1" /> |
The second thing is to create your Identity Role Store model and Identity User Store model. Those models are responsible to manage the users and the roles when used in conjunction with Identity. Add a class named ApplicationIdentityRoleStore and ApplicationIdentityUserStore to your project. I’m assuming you already have a User model and Role model that you will use. In my case, I named them ApplicationUser and ApplicationRole. Role-based access control is one of Identity core functionality, along with users. If you don’t have roles in your system, it will be important that you define some. The RoleManager<T> class can be used in your controllers to manage roles in your application, but I won’t cover this in this post.
Role Store and User Store
In your role store model object, in my case ApplicationIdentityRoleStore, implement the IRoleStore<T>. This interface is responsible to give you access to manage your roles.
In your user store model object, in my case ApplicationIdentityUserStore, implement the IUserStore<T> and IUserRoleStore<T> interfaces. The IUserStore interface is responsible to give you access to manage your user account while the IUserRoleStore is responsible to map the users to the roles.
As you can see in these contracts (IRoleStore, IUserStore, IUserRoleStore) there are methods that Identity uses internally to do what it needs. Implement the methods that reflect your business needs.
You can also make use of Dependency Injection (DI) to inject whatever you need in those objects. For instance, you can inject an Entity Framework DBContext but it can be anything you wish to access your data.
Open to extension
You can add more functionalities, to complete your setup, by implementing the other available interfaces. Here is some worth of mention. To see all of them, you can check out the code for Identity here or the documentation.
IUserClaimStore: provides the management for claims for a user
IUserLockoutStore: provides a way to store information which can be used to implement account lockout, including access failures and lockout status
IUserTwoFactorStore: provides a way to store a flag indicating whether a user has two factor authentication enabled. To see how to use TwoFactor authentication, see my post here.
IUserEmailStore: provides a way for the storage and management of user email addresses
IUserPasswordStore: provides a way for the management of users’ password hashes
Plugging it in into the pipeline
To be able to add Identity into your ASP.NET pipeline, add the following in your ConfigureServices method in your Startup class and replace the appropriate objects with the corresponding ones in your model. The AddIdentity method can take as parameter a IdentityOptions expression. The IdentityOptions class represents all the options you can use to configure the identity system. Define it if you need to change identity default system options.
1 2 3 |
services.AddIdentity<ApplicationUser, ApplicationRole>() .AddUserStore<ApplicationIdentityUserStore>() .AddRoleStore<ApplicationIdentityRoleStore>(); |
In the Configure method, add app.UseAuthentication() before the UseMVC method. Order is important.
Using it in your Controllers
Once all of this has been wired up, you can make use of your model objects, to manage the sign-in and the users, with Identity’s SignInManager<T> and UserManager<T> objects. All of this is done by passing, using DI, the UserManager<T>, SignInManager<T> objects (where T is your User model, i.e. in my case ApplicationUser) in your controller’s constructor. You can then access the methods in those objects to do user management and sign-in management all which will be wired through Identity and by that means, through your custom implementation.
Yea but I have custom passwords that were not generated by Identity…
Don’t fear my friend, you can tell Identity to continue to use (and verify) your hashing by extending the PasswordHasher<T> class into your custom implementation. Add IPasswordHasher<T> to your DI pipeline (before registering Identity) and you should be good to go. Andrew Lock wrote about this in a detailed post of his. Check it out! However, be careful that you may introduce security issues by having a custom implementation.
Note of mention
When defining your user store, be sure to implement IUserSecurityStampStore.GetSecurityStampAsync method. By default, every 30 minutes, the security stamp is validated. This mostly due to the fact that sign-in everywhere is an option. The security stamp is updated usually in identity (it is a property that is persisted) when the user changes password for instance. This will make all the locations where the user has signed on (except the one where he changed his password) sign out after 30mins because the stamp (usually a guid) has changed.