How to Customize Asp.NET Identity Roles with External Database Storage step by step

ASP.NET 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 User and Role Identity Provider  using ASP.NET MVC5.

This post, is a continuation of  How to Customize Asp.NET Identity with External Database Storage step by step, including Roles implementation

Create Asp.NET MVC Project

Open Visual Studio and create an ASP.NET Web application project and choose MVC template

Create aspnet mvc5 project

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

register

Open app_data folder in solution explorer  and open containing folder

appdatafolder

identitydatabasefolder

We will see that a database is created at runtime and named aspnet-AspNetIdentity-20160402093629, asp.net will use that database to store security informations like user, passwords, roles, etc…

Note that aspnet-AspNetIdentity-2016040209362 is not created on Database Server but as attached file. So, we will move database on SQL Server and use it to customize Asp.Net identity.

Lets use visual Studio and click View, then SQL Server Object Explorer, select database and expand tables

5 tables has been created : AspNetRoles, AspNetUserClaims, AspNetUserLogins, AspNetUserRoles, AspNetUsers

identitydatabaseProjectfolder

And the registered user has been inserted on AspNetUsers table

usertable

Our Identity Storage is now ready, we will come back later to data structure

Now , open SQL Server Express and attach the identity Database created earlier

Right click databases and click attach , browse and choose the database so as to store  aspnet-AspNetIdentity-20160402093629 in SQL Server

We can explore our DataBase Model by creating a database diagram  as follow

DatabaseDiagramme

Finally, open web.config file and change  connectionstrings to target Sql Server Database as follow

ConnectionString

Lets press F5 and run our application  to register and login a  new user

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 IdentityLibrary

Right click reference and select Manage Nuget Packages

Untitled

Install package Microsoft.AspNet.Identity.Core  and Microsoft.AspNet.Identity.EntityFramework

Image sans titre

3

Create an IdentityUser class to hold user informations, it inherits from IdentityUser

2

Create a IdentityRole class to hold user roles informations

Image sans titre2

Lets create a UserStore class, Asp.NET MVC use it  by default

var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));

But we will implement it to  customize our storage provider
1

Lets Create a RoleStore Class and implement IRoleIdentity

4

Create an Entity Framework dataModel

Right click IdentityLibrary project and add a new item , select ADO.NET Entity Data Model and click Add

Select Code First From Database and click next

2

If we have already a dataconnection, we can choose it or create a new connection, select Microsoft SQL Server and clic Continue

3

Select Server Name, use Windows Authentication or SQL Server Authentication  ( with credentials) , then select database and then click OK

4

Check  database tables and generate our model

5

So, 5 classes are created in IdentityLibrary  project and mapped to database tables, we will these classes to manage object

Image sans titre

Configure Asp.Net client to target our custom Identity Library

Image sans titre5

image-sans-titre6

Press F5  to run application,  and  register a new account

6

We will see that, our new framwork is targeting by asp.net client

Implement custom User Identity

  • Implement UserStore 

/// <summary>
/// Class that implements the key ASP.NET Identity user store iterfaces
/// </summary>
public class UserStore<T> : IUserRoleStore<T>,
IUserStore<T>,
IUserPasswordStore<T>,
IUserEmailStore<T>,
IUserLockoutStore<T, string>,
IUserTwoFactorStore<T, string>
where T : IdentityUser
{
private readonly UserRepository<T> _userTable;
private readonly UserRolesRepository _userRolesTable;

public UserStore(DatabaseContext databaseContext)
{
_userTable = new UserRepository<T>(databaseContext);
_userRolesTable = new UserRolesRepository(databaseContext);
}

public Task CreateAsync(T user)
{
if (user == null)
{
throw new ArgumentNullException(“user”);
}
return Task.Run(() => _userTable.Insert(user));
}

public Task<T> FindByIdAsync(string userId)
{
if (string.IsNullOrEmpty(userId))
{
throw new ArgumentException(“Null or empty argument: userId”);
}

return Task.Run(() => _userTable.GeTById(userId));
}

public Task<bool> GetTwoFactorEnabledAsync(T user)
{
return Task.FromResult(user.TwoFactorEnabled);
}

public Task<T> FindByNameAsync(string userName)
{
if (string.IsNullOrEmpty(userName))
{
throw new ArgumentException(“Null or empty argument: userName”);
}

return Task.Run(() => _userTable.GeTByName(userName));
}

public Task<IList<string>> GetRolesAsync(T user)
{
if (user == null)
{
throw new ArgumentNullException(“user”);
}

return Task.Run(() => _userRolesTable.FindByUserId(user.Id));
}

public Task<string> GetPasswordHashAsync(T user)
{
return Task.Run(() => _userTable.GetPasswordHash(user.Id));
}

public Task SetPasswordHashAsync(T user, string passwordHash)
{
return Task.Run(() => user.PasswordHash = passwordHash);
}

public Task<T> FindByEmailAsync(string email)
{
if (String.IsNullOrEmpty(email))
{
throw new ArgumentNullException(“email”);
}

return Task.Run(() => _userTable.GeTByEmail(email));
}

public Task<string> GetEmailAsync(T user)
{
return Task.FromResult(user.Email);
}

public Task<int> GetAccessFailedCountAsync(T user)
{
return Task.FromResult(user.AccessFailedCount);
}

public Task<bool> GetLockoutEnabledAsync(T user)
{
return Task.FromResult(user.LockoutEnabled);
}

public Task<DateTimeOffset> GetLockoutEndDateAsync(T user)
{
return
Task.FromResult(user.LockoutEndDateUtc.HasValue
? new DateTimeOffset(DateTime.SpecifyKind(user.LockoutEndDateUtc.Value, DateTimeKind.Utc))
: new DateTimeOffset());
}

public Task SetLockoutEnabledAsync(T user, bool enabled)
{
user.LockoutEnabled = enabled;

return Task.Run(() => _userTable.Update(user));
}

public Task SetLockoutEndDateAsync(T user, DateTimeOffset lockoutEnd)
{
throw new NotImplementedException();
}

public Task SetTwoFactorEnabledAsync(T user, bool enabled)
{
throw new NotImplementedException();
}

public Task DeleteAsync(T user)
{
throw new NotImplementedException();
}

public Task<int> IncrementAccessFailedCountAsync(T user)
{
throw new NotImplementedException();
}

public Task ResetAccessFailedCountAsync(T user)
{
throw new NotImplementedException();
}

public Task<bool> GetEmailConfirmedAsync(T user)
{
throw new NotImplementedException();
}

public Task SetEmailAsync(T user, string email)
{
throw new NotImplementedException();
}

public Task SetEmailConfirmedAsync(T user, bool confirmed)
{
throw new NotImplementedException();
}

public Task<bool> IsInRoleAsync(T user, string roleName)
{
throw new NotImplementedException();
}

public Task RemoveFromRoleAsync(T user, string roleName)
{
throw new NotImplementedException();
}

public Task<bool> HasPasswordAsync(T user)
{
throw new NotImplementedException();
}

public Task UpdateAsync(T user)
{
throw new NotImplementedException();
}

public Task AddToRoleAsync(T user, string roleName)
{
throw new NotImplementedException();
}

public void Dispose()
{
//throw new NotImplementedException();
}
}

  • Create a class UserRepository  and implement it  

2

public class UserRepository<T> where T : IdentityUser
{
private readonly DatabaseContext _databaseContext;

public UserRepository(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}

internal T GeTByName(string userName)
{
var user = _databaseContext.AspNetUsers.SingleOrDefault(u => u.UserName == userName);
if (user != null)
{
T result = (T)Activator.CreateInstance(typeof(T));
result.Id = user.Id;
result.UserName = user.UserName;
result.PasswordHash = user.PasswordHash;
result.SecurityStamp = user.SecurityStamp;
result.Email = result.Email;
result.EmailConfirmed = user.EmailConfirmed;
result.PhoneNumber = user.PhoneNumber;
result.PhoneNumberConfirmed = user.PhoneNumberConfirmed;
result.LockoutEnabled = user.LockoutEnabled;
result.LockoutEndDateUtc = user.LockoutEndDateUtc;
result.AccessFailedCount = user.AccessFailedCount;
return result;
}
return null;
}

internal T GeTByEmail(string email)
{
var user = _databaseContext.AspNetUsers.SingleOrDefault(u => u.Email == email);
if (user != null)
{
T result = (T)Activator.CreateInstance(typeof(T));

result.Id = user.Id;
result.UserName = user.UserName;
result.PasswordHash = user.PasswordHash;
result.SecurityStamp = user.SecurityStamp;
result.Email = result.Email;
result.EmailConfirmed = user.EmailConfirmed;
result.PhoneNumber = user.PhoneNumber;
result.PhoneNumberConfirmed = user.PhoneNumberConfirmed;
result.LockoutEnabled = user.LockoutEnabled;
result.LockoutEndDateUtc = user.LockoutEndDateUtc;
result.AccessFailedCount = user.AccessFailedCount;
return result;
}
return null;
}

internal int Insert(T user)
{
_databaseContext.AspNetUsers.Add(new AspNetUsers
{
Id = user.Id,
UserName = user.UserName,
PasswordHash = user.PasswordHash,
SecurityStamp = user.SecurityStamp,
Email = user.Email,
EmailConfirmed = user.EmailConfirmed,
PhoneNumber = user.PhoneNumber,
PhoneNumberConfirmed = user.PhoneNumberConfirmed,
LockoutEnabled = user.LockoutEnabled,
LockoutEndDateUtc = user.LockoutEndDateUtc,
AccessFailedCount = user.AccessFailedCount
});

return _databaseContext.SaveChanges();
}

/// <summary>
/// Returns an T given the user’s id
/// </summary>
/// <param name=”userId”>The user’s id</param>
/// <returns></returns>
public T GeTById(string userId)
{
var user = _databaseContext.AspNetUsers.Find(userId);
T result = (T)Activator.CreateInstance(typeof(T));

result.Id = user.Id;
result.UserName = user.UserName;
result.PasswordHash = user.PasswordHash;
result.SecurityStamp = user.SecurityStamp;
result.Email = result.Email;
result.EmailConfirmed = user.EmailConfirmed;
result.PhoneNumber = user.PhoneNumber;
result.PhoneNumberConfirmed = user.PhoneNumberConfirmed;
result.LockoutEnabled = user.LockoutEnabled;
result.LockoutEndDateUtc = user.LockoutEndDateUtc;
result.AccessFailedCount = user.AccessFailedCount;
return result;
}

/// <summary>
/// Return the user’s password hash
/// </summary>
/// <param name=”userId”>The user’s id</param>
/// <returns></returns>
public string GetPasswordHash(string userId)
{
var user = _databaseContext.AspNetUsers.FirstOrDefault(u => u.Id == userId);
var passHash = user != null ? user.PasswordHash : null;
return passHash;
}

/// <summary>
/// Updates a user in the Users table
/// </summary>
/// <param name=”user”></param>
/// <returns></returns>
public int Update(T user)
{
var result = _databaseContext.AspNetUsers.FirstOrDefault(u => u.Id == user.Id);
if (result != null)
{
result.UserName = user.UserName;
result.PasswordHash = user.PasswordHash;
result.SecurityStamp = user.SecurityStamp;
result.Email = result.Email;
result.EmailConfirmed = user.EmailConfirmed;
result.PhoneNumber = user.PhoneNumber;
result.PhoneNumberConfirmed = user.PhoneNumberConfirmed;
result.LockoutEnabled = user.LockoutEnabled;
result.LockoutEndDateUtc = user.LockoutEndDateUtc;
result.AccessFailedCount = user.AccessFailedCount;
return _databaseContext.SaveChanges();
}
return 0;
}
}

  • Create a  UserRolesRepository class and implement it

3

Our asp.net client application is now ready  to use IdentityLibrary to register, and it enable us to login and logout user

Implement custom Role Identity

Open IdentityConfig.cs file and add an ApplicationRoleManager class

1

Create and implement a RoleStore>T> class that inherits IdentityRole

/// <summary>
/// Class that implements the key ASP.NET Identity role store iterfaces
/// </summary>
public class RoleStore<TRole> : IQueryableRoleStore<TRole>
where TRole : IdentityRole
{
private readonly RoleRepository<TRole> _roleRepository;

public RoleStore(DatabaseContext databaseContext)
{
_roleRepository = new RoleRepository<TRole>(databaseContext);
}

public IQueryable<TRole> Roles
{
get
{
return _roleRepository.GetRoles();
}
}

public Task CreateAsync(TRole role)
{
if (role == null)
{
throw new ArgumentNullException(“role”);
}
return Task.Run(() => _roleRepository.Create(role));
}

public Task DeleteAsync(TRole role)
{
if (role == null)
{
throw new ArgumentNullException(“role”);
}

return Task.Run(() => _roleRepository.Delete(role.Id));
}

public Task<TRole> FindByIdAsync(string roleId)
{
if (roleId == null)
{
throw new ArgumentNullException(“roleId”);
}

return Task.Run(() => _roleRepository.FindById(roleId));
}

public Task<TRole> FindByNameAsync(string roleName)
{
if (roleName == null)
{
throw new ArgumentNullException(“roleName”);
}

return Task.Run(() => _roleRepository.FindByNamec(roleName));
}

public Task UpdateAsync(TRole role)
{
if (role == null)
{
throw new ArgumentNullException(“role”);
}
return Task.Run(() => _roleRepository.Update(role));
}

public void Dispose()
{
_roleRepository.Dispose();
}
}

RoleSotre user RoleRepository , so create a RoleRepository class and implement it :

internal class RoleRepository<T> where T : IdentityRole
{
private readonly DatabaseContext _databaseContext;

public RoleRepository(DatabaseContext dataBaseContext)
{
_databaseContext = dataBaseContext;
}

internal IQueryable<T> GetRoles()
{
List<T> result = (List<T>)Activator.CreateInstance(typeof(List<T>));
var roles = _databaseContext.AspNetRoles.ToList();
foreach (var role in roles)
{
T item = (T)Activator.CreateInstance(typeof(T));
item.Id = role.Id;
item.Name = role.Name;
item.Description = role.Description;
result.Add(item);
}
return result.AsQueryable();
}

internal void Dispose()
{
_databaseContext.Dispose();
}

internal int Create(T role)
{
_databaseContext.AspNetRoles.Add(new AspNetRoles
{
Id = role.Id,
Description = role.Description,
Name = role.Name
});
return _databaseContext.SaveChanges();
}

internal int Delete(string id)
{
var existingRole = _databaseContext.AspNetRoles.Find(id.Trim());
if (existingRole != null)
{
_databaseContext.Entry(existingRole).State = EntityState.Deleted;
return _databaseContext.SaveChanges();
}
return -1;
}

internal T FindByNamec(string roleName)
{
var role = _databaseContext.AspNetRoles.FirstOrDefault(r => r.Name == roleName.Trim());
if (role == null)
{
return default(T);
}
T item = (T)Activator.CreateInstance(typeof(T));
item.Id = role.Id;
item.Name = role.Name;
item.Description = role.Description;
return item;
}

internal T FindById(string roleId)
{
var role = _databaseContext.AspNetRoles.Find(roleId.Trim());
if (role == null)
{
return default(T);
}
T item = (T)Activator.CreateInstance(typeof(T));
item.Id = role.Id;
item.Name = role.Name;
item.Description = role.Description;
return item;
}

internal int Update(T role)
{
var existingRole = _databaseContext.AspNetRoles.Find(role.Id.Trim());
if (existingRole != null)
{
existingRole.Name = role.Name;
existingRole.Description = role.Description;
_databaseContext.Entry(existingRole).State = EntityState.Modified;
return _databaseContext.SaveChanges();
}
return -1;
}
}

RUN IT

create an admin user using the following script
user name = admin@yopmail.com

Password = Admin1#

USE [AspNetIdentity]
GO

INSERT [dbo].[AspNetRoles] ([Id], [Name], [Description]) VALUES (N’admin’, N’Admin’, N’Administrator’)
GO
INSERT [dbo].[AspNetUsers] ([Id], [Email], [EmailConfirmed], [PasswordHash], [SecurityStamp], [PhoneNumber], [PhoneNumberConfirmed], [TwoFactorEnabled], [LockoutEndDateUtc], [LockoutEnabled], [AccessFailedCount], [UserName]) VALUES (N’d5452813-d698-4a62-91a9-14efb1743c7e’, N’admin@yopmail.com’, 0, N’AOtXU3XwTkD7OgNMQsGrRVB355ZvctkxV2WxY5TgFkVB5q8RxP88LyHje5u/Gh5ymQ==’, NULL, NULL, 0, 0, NULL, 1, 0, N’admin@yopmail.com’)
GO
INSERT [dbo].[AspNetUserRoles] ([RoleId], [UserId]) VALUES (N’admin’, N’d5452813-d698-4a62-91a9-14efb1743c7e’)
GO

23

4

this post is Highlighted in https://www.asp.net/community/articles  on 26/02/2017

Download source code

In our next tutorial, we will talk about OWIN and CATANA.

Best regards.

Gora LEYE

I'm a microsoft most valuable professional (MVP) .NET Architect and Technical Expert skills located in Paris (FRANCE). The purpose of this blog is mainly to post general .NET tips and tricks, www.masterconduite.com Gora LEYE

Support us

BMC logoBuy me a coffee