A while ago I wrote a blog post about the preview version of ASP.NET Identity. Now its time to see what the final version 1.0 looks like. As we will see the library has evolved quite a bit since the preview.
ASP.NET Identity provides an implementation of user and and rolemanagement. ASP.NET Identity does not do authentication. Cookie based authentication and redirects to external login providers such as Google and Facebook is handeled by the OWIN libraries. ASP.NET Identity is only concerned with creation of users and roles, and persisting information about users such as passwords, links to external login providers and claims. This functionality is provided by classes in the two assemblies
- Microsoft.AspNet.Identity.Core - Core user and role management logic
- Microsoft.AspNet.Identity.EntityFramework - EnitityFramework based persistence of Users, Roles, Claims etc.
Identity Core - user management logic
Identity.Core has two main classes which will be our main means to interact with ASP.NET Identity. They are named UserManager and RoleManager. UserManager is probably the most important one, and is the only one that are used in the AccountController that is created by the default templates for e.g. an MVC when you choose "Individual User Accounts".
The UserManager is used to add/create, find/get and remove:
- Link to roles
- Link to logins
A UserStore that is responsible for the persistence is injected into the UserManager. The usermanager will use the UserStore to perform all it's needs for persistence. The UserManager is a generic class that has a TUser type parameter. TUser must be a class that implements the IUser interface, which means that a User class needs to have a Id getter of string type and a UserName string property.
Developers often asks why Microsoft chose to type the Id as string instead of int. Hao Kung who is a developer on the ASP.NET team and works on ASP.NET Identity in Microsoft explained it like this on stackoverflow:
"So we decided on string keys to avoid having to deal with key serialization issues, the EF default implementation could have used ints as the primary key, GUIDs were just an easy way to generate a random unique string key".
It is true that if we implement our own UserStore then we are free to use a different type for our primary key, but it is a bit inconvenient that this key can not be named "Id". IUser which we must implement already defines a string Id get'er.
Identity EntityFramework - Entity framework code first persistence
The classes of the Identity Core assembly require a persistence mechanism. Microsoft has provided a Entity Framework code first based implementation to us in the assembly Microsoft.AspNet.Identity. The Identity EntityFramework assembly provides a UserStore implementation that can be plugged right into the UserManager from the Core assembly. This is by far our easiest option to get started with ASP.NET Identity. In fact if you create a new MVC project and choose Individual User Accounts the template will create a working implementation for you right away.
If you choose to use the UserStore class from the Identity EntityFramework assembly and it's default constructor you will get the following persistence model:
As shown in the diagram above the model consist of a IdentityUser and the following related objects:
A list of claims for the user
This is only used for external logins. Links the local user to an external account
If you use roles this is where the roles and mapping from user to roles will be stored.
Simplicity comes at a price
This simplicity comes at a price though. The UserStore require us to not only have our user class implement the IUser interface, it also demands that we need to inherit from the ASP.NET Identity EntityFramework class called IdentityUser which adds two new properties to the IUser interface. The hashed password and a SecurityStamp is stored on the user. Many developers and myself included are not too happy about being forced to have a reference to EntityFramework and ASP.NET EntityFramework from our domain model assembly and that we are required to inherit from this IdentityUser class, but if we want to have the simplicity of just using the Microsoft Identity persistence then we just have to swallow that one
The UserStore will by default create a DbContext that in turn will create five tables for us, one for each of the entities in the model.
AspNetUsers- for storing
AspNetUserClaims- for storing
AspNetUserLogin- for storing
AspNetRoles- for storing
AspNetUserRoles- for storing the many-to-many relation between
The table names and the Entity Framework configuration of relations between the entities are configured in the OnModelCreating method of the IdentityDbContext that the UserStore will create if we don't provide our own DbContext. If we want to use different table names or map things differently in our database we need to provide our own DbContext. We can either create a completely new DbContext class with the 5 required DbSets (one for each of the model classes) or we can simply subclass the IdentityDbContext and override the OnModelCreating method where we can configure the EF mappings as we wish.
As I wrote in my previous post I was hoping that Microsoft would remove the requirement of having to inherit the IdentityUser. As we have seen, it didn't happen.
If we want to use the stock implementation in the form of ASP.NET Identity EntityFramework we have to inherit from the IdentityUser class. Still, what they did do was refactor the implementation into two clearly separate concerns. The core assembly containing the core logic and the EntityFramework assembly containing EF persistence. It's the latter that forces me to do things I don't like, and because the designers has made this clean separation it is possible for me to opt out of the EF persistence implementation while still use the user management logic in the Core assembly. In my next post I will show how to write my own UserStore class. To show that the ASP.NET Identity model is flexible and in no way tied to Entity Framework or SQL server I will be persisting my user data to a MongoDatabase instead of using Entity Framework.