Language

Overview of Custom Storage Providers for ASP.NET Identity

By Tom FitzMacken|

ASP.NET Identity is an extensible system which enables you to create your own storage provider and plug it into your application without re-working the application. This topic describes how to create a customized storage provider for ASP.NET Identity. It covers the important concepts for creating your own storage provider, but it is not step-by-step walkthrough of implementing a custom storage provider.

For an example of implementing a custom storage provider, see Implementing a Custom MySQL ASP.NET Identity Storage Provider.

Introduction

By default, the ASP.NET Identity system stores user information in a SQL Server database, and uses Entity Framework Code First to create the database. For many applications, this approach works well. However, you may prefer to use a different type of persistence mechanism, such as Windows Azure Table Storage, or you may already have database tables with a very different structure than the default implementation. In either case, you can write a customized provider for your storage mechanism and plug that provider into your application.

ASP.NET Identity is included by default in many of the Visual Studio 2013 templates. You can get updates to ASP.NET Identity through Microsoft AspNet Identity EntityFramework NuGet package.

This topic includes the following sections:

Understand the architecture

ASP.NET Identity consists of classes called managers and stores. Managers are high-level classes which an application developer uses to perform operations, such as creating a user, in the ASP.NET Identity system. Stores are lower-level classes that specify how entities, such as users and roles, are persisted. Stores are closely coupled with the persistence mechanism, but managers are decoupled from stores which means you can replace the persistence mechanism without disrupting the entire application.

The following diagram shows how your web application interacts with the managers, and stores interact with the data access layer.

To create a customized storage provider for ASP.NET Identity, you have to create the data source, the data access layer, and the store classes that interact with this data access layer. You can continue using the same manager APIs to perform data operations on the user but now that data is saved to a different storage system.

Understand the data that is stored

To implement a custom storage provider, you must understand the types of data used with ASP.NET Identity, and decide which features are relevant to your application.

DataDescription
UsersRegistered users of your web site. Includes the user Id and user name. Might include a hashed password if users log in with credentials that are specific to your site (rather than using credentials from an external site like Facebook), and security stamp to indicate whether anything has changed in the user credentials.
User ClaimsA set of statements (or claims) about the user that represent the user's identity. Can enable greater expression of the user's identity than can be achieved through roles.
User LoginsInformation about the external authentication provider (like Facebook) to use when logging in a user.
RolesAuthorization groups for your site. Includes the role Id and role name (like "Admin" or "Employee").

Create the data access layer

This topic assumes you are familiar with the persistence mechanism that you are going to use and how to create entities for that mechanism. This topic does not provide details about how to create the repositories or data access classes; instead, it provides some suggestions about the design decisions you need to make when working with ASP.NET Identity.

You have a lot of freedom when designing the repositories for a customized store provider. You only need to create repositories for features that you intend to use in your application. For example, if you are not using roles in your application, you do not need to create storage for roles or user roles. Your technology and existing infrastructure may require a structure that is very different from the default implementation of ASP.NET Identity. In your data access layer, you provide the logic to work with the structure of your repositories.

For a MySQL implemention of data repositories, see MySQLIdentity.sql.

In the data access layer, you provide the logic to save the data from ASP.NET Identity to your data source. The data access layer for your customized storage provider might include the following classes to store user and role information:

ClassDescriptionExample
Primary Encapsulates the information to connect to your persistence mechanism and execute queries. This class is central to your data access layer. The other data classes will require an instance of this class to perform their operations. You will also initialize your store classes with an instance of this class. MySQLDatabase
User Storage Stores and retrieves user information (such as user name and password hash). UserTable (MySQL)
Role Storage Stores and retrieves role information (such as the role name). RoleTable (MySQL)
UserClaims Storage Stores and retrieves user claim information (such as the claim type and value). UserClaimsTable (MySQL)
UserLogins Storage Stores and retrieves user login information (such as an external authentication provider). UserLoginsTable (MySQL)
UserRole Storage Stores and retrieves which roles a user is assigned to. UserRoleTable (MySQL)

Again, you only need to implement the classes that you intend to use in your application.

In the data access classes, you provide code to perform data operations for your particular persistence mechanism. For example, within the MySQL implementation, the UserTable class contains a method to insert a new record into the Users database table. The variable named _database is an instance of the MySQLDatabase class.

public int Insert(IdentityUser user)
{
    string commandText = "Insert into Users (UserName, Id, PasswordHash, SecurityStamp) values (@name, @id, @pwdHash, @SecStamp)";
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("@name", user.UserName);
    parameters.Add("@id", user.Id);
    parameters.Add("@pwdHash", user.PasswordHash);
    parameters.Add("@SecStamp", user.SecurityStamp);

    return _database.Execute(commandText, parameters);
}

After creating your data access classes, you must create store classes that call the specific methods in the data access layer.

Customize the user store and user

When implementing your own storage provider, you must implement user storage classes which are equivalent to the following classes in the Microsoft.ASP.NET.Identity.EntityFramework namespace:

The following image shows a class diagram of the EntityFramework default implementation of users.

The interfaces are important because they define the properties and methods that the UserManager attempts to call when performing requested operations.

Interface and class to customize the user

  • IUser
    The IUser interface defines the members that must be implemented for the user class. It only contains two properties - Id and UserName.
  • IdentityUser
    This class represents all of the properties for a user on your web site. To customize IdentityUser, create a user class that implements the IUser interface, and define any additional properties or constructors that are needed.
    public class IdentityUser : IUser
    {
        public IdentityUser() { ... }
        public IdentityUser(string userName) { ... }
        public string Id { get; set; }
        public string UserName { get; set; }
        // can also define optional properties such as:
        //    PasswordHash
        //    SecurityStamp
        //    Claims
        //    Logins
        //    Roles
    }
    
    For a complete implementation, see IdentityUser (MySQL).

Interfaces and class to customize user store

  • IUserStore<TUser>
    The IUserStore<TUser> interface is the only interface you must implement in your user store. It defines methods for creating, updating, deleting, and retrieving users.
  • IUserClaimStore<TUser>
    The IUserClaimStore interface defines the methods you must implement in your user store to enable user claims. It contains methods or adding, removing and retrieving user claims.
  • IUserLoginStore<TUser>
    The IUserLoginStore defines the methods you must implement in your user store to enable external authentication providers. It contains methods for adding, removing and retrieving user logins, and a method for retrieving a user based on the login information.
  • IUserRoleStore<TUser>
    The IUserRoleStore interface defines the methods you must implement in your user store to map a user to a role. It contains methods to add, remove, and retrieve a user's roles, and a method to check if a user is assigned to a role.
  • IUserPasswordStore<TUser>
    The IUserPasswordStore interface defines the methods you must implement in your user store to persist hashed passwords. It contains methods for getting and setting the hashed password, and a method that indicates whether the user has set a password.
  • IUserSecurityStampStore<TUser>
    The IUserSecurityStampStore interface defines the methods you must implement in your user store to use a security stamp for indicating whether the user's credentials have changed. It contains methods for getting and setting the security stamp.
  • UserStore<TUser>
    The UserStore class provides the methods you use for all data operations on the user. To customize UserStore, create a class that, at minimum, implements the IUserStore interface. The generic parameter takes the type of your user class which usually is the IdentityUser class you defined.
    public class UserStore : IUserStore<IdentityUser>
    {
        public UserStore() { ... }
        public UserStore(ExampleStorage database) { ... }
        public Task CreateAsync(IdentityUser user) { ... }
        public Task DeleteAsync(IdentityUser user) { ... }
        public Task<IdentityUser> FindByIdAsync(string userId) { ... }
        public Task<IdentityUser> FindByNameAsync(string userName) { ... }
        public Task UpdateAsync(IdentityUser user) { ... }
        public void Dispose() { ... }
    }
    
    The constructor that takes a parameter named database of type ExampleDatabase is only an illustration of how to pass in your data access class. For example, in the MySQL implementation, this constructor takes a parameter of type MySQLDatabase.

    Optionally, you can also implement the IUserClaimStore, IUserLoginStore, IUserRoleStore, IUserPasswordStore, and IUserSecurityStampStore interfaces if you need these functionalities in your application.
    public class UserStore : IUserStore<IdentityUser>,
                             IUserClaimStore<IdentityUser>,
                             IUserLoginStore<IdentityUser>,
                             IUserRoleStore<IdentityUser>,
                             IUserPasswordStore<IdentityUser>,
                             IUserSecurityStampStore<IdentityUser>
    {
        // interface implementations not shown
    }
    
    Within your UserStore class, you use the data access classes that you created to perform operations. For example, in the MySQL implementation, the UserStore class has the CreateAsync method which uses an instance of UserTable to insert a new record. The Insert method on the userTable object is the same method that was shown in the previous section.
    public Task CreateAsync(IdentityUser user)
    {
        if (user == null) {
            throw new ArgumentNullException("user");
        }
    
        userTable.Insert(user);
    
        return Task.FromResult<object>(null);
    }
    

    For a complete implementation (including all of interfaces), see UserStore (MySQL).

IdentityUserClaim, IdentityUserLogin, and IdentityUserRole

The IdentityUserClaim, IdentityUserLogin, and IdentityUserRole classes contain properties for values related to these features. If you are using these features, you may want to create your own version of these classes and define the properties for your application.

In the MySQL Storage Provider sample, IdentityUserClaim, IdentityUserLogin, and IdentityUserRole are not implemented. This decision was made because it is more efficient to not load these database entries into memory to perform basic operations (such as adding or removing a user’s claim). Instead, the backend store classes execute these operations directly on the database. For example, the UserStore.GetClaimsAsync() method calls the userClaimTable.FindByUserId(user.Id) method to executes a query on that table directly and return a list of claims.

public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
{
    ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
    return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
}

Customize the role store

When implementing your own storage provider, you must implement role storage classes which are equivalent to the following classes in the Microsoft.ASP.NET.Identity.EntityFramework namespace:

The next image shows a class diagram of the EntityFramework default implementation of roles.

Interface and class to customize role

  • IRole
    The IRole interface defines the members that must be implemented for the role class. It contains only two properties - Id and Name.
  • IdentityRole
    To customize IdentityRole, create a class that implements the IRole interface.

    public class IdentityRole : IRole
    {
        public IdentityRole() { ... }
        public IdentityRole(string roleName) { ... }
        public string Id { get; set; }
        public string Name { get; set; }
    }
    

    For a complete implementation, see IdentityRole (MySQL).

Interface and class to customize role store

  • IRoleStore<TRole>
    The IRoleStore interface defines the methods to implement in your role store class. It contains methods for creating, updating, deleting and retrieving roles.
  • RoleStore<TRole>
    To customize RoleStore, create a class that implements the IRoleStore interface. You only have to implement this class if want to use roles on your system.
    public class RoleStore : IRoleStore<TRole>
    {
        public RoleStore() { ... }
        public RoleStore(ExampleStorage database) { ... }
        public Task CreateAsync(IdentityUser role) { ... }
        public Task DeleteAsync(IdentityUser role) { ... }
        public Task<IdentityUser> FindByIdAsync(string roleId) { ... }
        public Task<IdentityUser> FindByNameAsync(string roleName) { ... }
        public Task UpdateAsync(IdentityUser role) { ... }
        public void Dispose() { ... }
    }
    
    The constructor that takes a parameter named database of type ExampleDatabase is only an illustration of how to pass in your data access class. For example, in the MySQL implementation, this constructor takes a parameter of type MySQLDatabase.

    For a complete implementations, see RoleStore (MySQL).

Reconfigure application to use new storage provider

You have implemented your new storage provider. Now, you must configure your application to use this storage provider. If the default storage provider was included in your project, you must remove the default provider and replace it with your provider.

Replace default storage provider in MVC project

  1. In the Manage NuGet Packages window, uninstall the Microsoft ASP.NET Identity EntityFramework package. You can find this package by searching in the Installed packages for Identity.EntityFramework.
    You will be asked if you also want to uninstall Entity Framework. If you do not need it in other parts of your application, you can uninstall it.
  2. In the Models folder, delete IdentityModels.cs file.
  3. If your storage provider resides in a separate project, add a reference to it in your web application.
  4. Replace all reference to using Microsoft.AspNet.Identity.EntityFramework; with a using statement for the namespace of your storage provider.
  5. Replace all reference to ApplicationUser with IdentityUser.
  6. In the Controllers folder, open the AccountController.cs file and replace the constructor with the following code (instead of MySQLDatabase provide the name of the data access class that facilitates operations):
    public AccountController()
        : this(new UserManager<IdentityUser>(new UserStore(new MySQLDatabase())))
    {
    }
    
  7. If you have created any instances of RoleManager, change that code to use your new RoleStore class.
    var roleManager = new RoleManager<IdentityUser>(new RoleStore(new MySQLDatabase()));
    
  8. If needed, add the connection string to the Web.config file.

Replace default storage provider in Web Forms project

  1. In the Manage NuGet Packages window, uninstall the Microsoft ASP.NET Identity EntityFramework package. You can find this package by searching in the Installed packages for Identity.EntityFramework.
    You will be asked if you also want to uninstall Entity Framework. If you do not need it in other parts of your application, you can uninstall it.
  2. In the Models folder, open the IdentityModels.cs file, and delete or comment out the following code.
    /*public class IdentityUser : IdentityUser
    {
    }
    
    public class ApplicationDbContext : IdentityDbContext<IdentityUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
    }*/
    
  3. If your storage provider resides in a separate project, add a reference to it in your web application.
  4. Replace all reference to using Microsoft.AspNet.Identity.EntityFramework; with a using statement for the namespace of your storage provider.
  5. Replace all reference to ApplicationUser with IdentityUser.
  6. In the IdentityModels.cs file, change the UserManager constructor to use your new provider:
    public class UserManager : UserManager<IdentityUser>
    {
        public UserManager()
            : base(new UserStore(new MySQLDatabase()))
        {
        }
    }
  7. If you have created any instances of RoleManager, change that code to use your new RoleStore class.
    var roleManager = new RoleManager<IdentityUser>(new RoleStore(new MySQLDatabase()));
    
  8. If needed, add the connection string to the Web.config file.

Other implementations of custom storage providers

These implementations can be installed through NuGet.

Author Information

Tom FitzMacken

Tom FitzMacken – Tom FitzMacken is a Senior Programming Writer on the Web Platform & Tools Content team.