How to Customize Asp.NET Identity Core with External Database Storage step by step
ASP.NET Core Identity is designed to enable us to easily use a number of different storage providers for our ASP.NET applications. We can use the supplied Identity providers that are included with the .NET Framework, or we can implement your own providers.
There are two primary reasons for creating a custom Identity provider.
We need to store Identity information in a data source that is not supported by the Identity providers included with the .NET Framework, such as a MysQL database, an Oracle database, or other data sources.
We need to manage Identity information using a database schema that is different from the database schema used by the providers that ship with the .NET Framework.
A common example of this would be to use authentication data that already exists in a SQL Server database for a company or Web site.
In this tutorial, we are going to implement and configure a custom Identity Provider using ASP.NET MVC Core and IndentityCore
Create Asp.NET Core with individual user account Project
Open Package Manager Console and run Update-Database.
The migration schema in folder Data/Migrations will be applied to create the database in localDB.
The connectionString is in file appSettings.json
Open Server explorer to view generated database tables : AspNetUsers, AspNetRoles, AspNetRoleClaims, AspNetUserClaims, AspNetUserLogins, AspNetUserRoles,AspNetUserTokens
Lets press F5 to run our projet and click on << Register >> link so as to create a new account as follow
Lets enter our email and our password and click register button
A new record is added to aspNetUsers table
Our application is now ready to use an external storage, on the next section, we will show you how to customize the external database storage
Create Identity Library Project
Lets create a Class Library project and name it IdentityCore.Library
Right click reference and select Manage Nuget Packages
Install package Microsoft.AspNet.Identity.Core and Microsoft.AspNetCore.Identity.EntityFrameworkCore
Create an MyIdentityUser class to hold user informations, it inherits from IdentityUser and an MyIdentityRole class that inherits from IdentityRole<string>
public class MyIdentityUser : IdentityUser { public DateTime? JoinDate { get; set; } public string JobTitle { get; set; } public string Contract { get; set; } public MyIdentityUser() { Id = Guid.NewGuid().ToString(); } }
MyIdentityRole class hold user roles informations
public class MyIdentityRole<T> : IdentityRole<string> { public MyIdentityRole() { Id = Guid.NewGuid().ToString(); } public MyIdentityRole(string name) : this() { Name = name; } public MyIdentityRole(string name, string id) { Name = name; Id = id; } public string Description { get; internal set; } }
we will implement MyUserStore to customize our storage provider
public class MyUserStore<T> : IUserStore<T> where T : MyIdentityUser { public Task<IdentityResult> CreateAsync(T user, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<IdentityResult> DeleteAsync(T user, CancellationToken cancellationToken) { throw new NotImplementedException(); } public void Dispose() { //throw new NotImplementedException(); } public Task<T> FindByIdAsync(string userId, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<T> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetNormalizedUserNameAsync(T user, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetUserIdAsync(T user, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetUserNameAsync(T user, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task SetNormalizedUserNameAsync(T user, string normalizedName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task SetUserNameAsync(T user, string userName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<IdentityResult> UpdateAsync(T user, CancellationToken cancellationToken) { throw new NotImplementedException(); } }
Lets Create a MyRoleStore Class and implement IRoleStore
public class MyRoleStore<T> : IRoleStore<T> where T : MyIdentityRole<string> { public Task<IdentityResult> CreateAsync(T role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<IdentityResult> DeleteAsync(T role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public void Dispose() { //throw new NotImplementedException(); } public Task<T> FindByIdAsync(string roleId, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<T> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetNormalizedRoleNameAsync(T role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetRoleIdAsync(T role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetRoleNameAsync(T role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task SetNormalizedRoleNameAsync(T role, string normalizedName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task SetRoleNameAsync(T role, string roleName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<IdentityResult> UpdateAsync(T role, CancellationToken cancellationToken) { throw new NotImplementedException(); } }
Configure Asp.Net client to target our custom Identity Library
Open Startup.cs file and remove the following code :
services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders();
Paste the following code : insteadof using ApplicationUser and IdentityRole, we are going to use : MyIdentityUser and MyIdentityRole
services.AddIdentity<MyIdentityUser, MyIdentityRole<string>>( config => { config.SignIn.RequireConfirmedEmail = true; }) .AddDefaultTokenProviders(); services.AddTransient<IUserStore<MyIdentityUser>, MyUserStore<MyIdentityUser>>(); services.AddTransient<IRoleStore<MyIdentityRole<string>>, MyRoleStore<MyIdentityRole<string>>>();
And finally replace all usage of ApplicationUser by MyIdentityUser
[Authorize] [Route("[controller]/[action]")] public class AccountController : Controller { private readonly UserManager<MyIdentityUser> _userManager; private readonly SignInManager<MyIdentityUser> _signInManager; private readonly IEmailSender _emailSender; private readonly ILogger _logger; public AccountController( UserManager<MyIdentityUser> userManager, SignInManager<MyIdentityUser> signInManager, IEmailSender emailSender, ILogger<AccountController> logger) { _userManager = userManager; _signInManager = signInManager; _emailSender = emailSender; _logger = logger; } }
Press F5 to run application, and register a new account
Thats all , we are using our new framwork to manage our custom storage logic
Best regards.