ASP.NET Core MVC Identity
The ASP.NET Core Identity is a web application membership management system that provides login functionality and security for a Web Application. It provides the base database tables needed to implement site security.
Implementation Steps
-
Create a project:
- ASP.NET Core Web App (Model-View-Controller)
- Enter a project name
- Choose a location
- Select .NET 9.0 (Standard Term Support) as a framework
- Configure for HTTPS - Checked
- Do not use top-level statements - Checked
-
Install NuGet Packages
- Microsoft.AspNetCore.Identity.EntityFrameworkCore
- Microsoft.AspNetCore.Identity.UI
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
-
Add ApplicationDbContext.cs to Models folder:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace <Project Name Here>.Models { public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } } } -
Add a database connection string to the appsettings.json file:
{ "ConnectionStrings": { "DBCS": "Data Source=DBServerName;Initial Catalog=DBName;user id=UserName;password=DBPassword;MultipleActiveResultSets=True;TrustServerCertificate=True" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" } -
Modify the Program.cs file.
using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using WebKokuaCore.Models; namespace WebKokuaCore { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); var connectionString = builder.Configuration.GetConnectionString("DBCS") ?? throw new InvalidOperationException("Connection string 'DBCS' not found."); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); builder.Services.AddIdentity<IdentityUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapStaticAssets(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}") .WithStaticAssets(); app.Run(); } } } -
Make changes to the database by entering the following into the Package Manager Console (Tools>NuGet Package Manager>Package Manager Console)
PM> Add-Migration IdentityMigration1 ... PM> update-database -
The following database tables will be created:
_EFMigrationsHistory MigrationId nvarchar(150) PK NOT NULL ProductVersion nvarchar(32) NOT NULL AspNetRoleClaims Id int PK NOT NULL RoleId nvarchar(450) FK NOT NULL ClaimType nvarchar(max) NULL ClaimValue nvarchar(max) NULL AspNetRoles Id nvarchar(450) PK NOT NULL Name nvarchar(256) NULL NormalizedName nvarchar(256) NULL ConcurrencyStamp nvarchar(max) NULL AspNetUserClaims Id int PK NOT NULL UserId nvarchar(450) FK NOT NULL ClaimType nvarchar(max) NULL ClaimValue nvarchar(max) NULL AspNetUserLogins LoginProvider nvarchar(450) PK NOT NULL ProviderKey nvarchar(450) PK NOT NULL ProviderDisplayName nvarchar(max) NULL UserId nvarchar(450) FK NOT NULL AspNetUserRoles UserId nvarchar(450) FK NOT NULL RoleId nvarchar(450) FK NOT NULL AspNetUsers Id nvarchar(450) PK NOT NULL UserName nvarchar(256) NULL NormalizedUserName nvarchar(256) NULL Email nvarchar(256) NULL NormalizedEmail nvarchar(256) NULL EmailConfirmed bit NOT NULL PasswordHash nvarchar(max) NULL SecurityStamp nvarchar(max) NULL ConcurrencyStamp nvarchar(max) NULL PhoneNumber nvarchar(max) NULL PhoneNumberConfirmed bit NOT NULL TwoFactorEnabled bit NOT NULL LockoutEnd datetimeoffset(7) NULL LockoutEnabled bit NOT NULL AccessFailedCount int NOT NULL AspNetUserTokens UserId nvarchar(450) FK NOT NULL LoginProvider nvarchar(450) NOT NULL Name nvarchar(450) NOT NULL Value nvarchar(max) NULL
Identity Table Descriptions
The following are the Identity tables created and what their columns represent.
AspNetUsers Table
The columns in the AspNetUsers table generally include the following:
- Id: The primary key for the user. A unique identifier for each user, typically a GUID string.
- UserName: The username of the user. It's unique and used for identification.
- NormalizedUserName: A normalized version of the username for consistent querying.
- Email: The user's email address.
- NormalizedEmail: A normalized version of the email for consistent querying, typically in uppercase. This is used in case-insensitive comparisons.
- EmailConfirmed: A boolean value indicates whether the user's email address has been confirmed.
- PasswordHash: The hashed version of the user's password. ASP.NET Core Identity uses a secure password hashing mechanism.
- SecurityStamp: A random value indicates whether any security-related user information has changed. For example, it changes when a user changes their password, resets the password, or adds an external login. Its primary purpose is to invalidate any existing sessions or cookies when security-related information changes. This is a security measure to ensure that the old sessions are no longer valid if credentials are compromised and then changed.
- ConcurrencyStamp: A unique value that changes whenever a user profile is updated, used for optimistic concurrency control, ensuring data integrity. In scenarios where multiple attempts to modify the same user record simultaneously, ConcurrencyStamp ensures that changes do not conflict.
- PhoneNumber: The user's phone number.
- PhoneNumberConfirmed: A boolean value indicates whether the user's phone number has been confirmed.
- TwoFactorEnabled: A boolean value indicates whether two-factor authentication is enabled for the user.
- LockoutEnd: The date and time when the lockout ends (if the user is currently locked out).
- LockoutEnabled: A boolean value indicates whether the account can be locked out for the user.
- AccessFailedCount: The number of failed login attempts. This is used for lockout functionality.
AspNetRoles Table
The columns in the AspNetRoles table generally include the following:
- Id: The primary key for the role. A unique identifier for each role, typically a GUID string.
- Name: The name of the role. This is the human-readable name used in your application code when assigning roles to users or authorizing users based on their roles.
- NormalizedName: The normalized version of the Name field, typically the uppercase version of the role name and is used in case-insensitive comparisons.
- ConcurrencyStamp: A unique stamp that handles concurrent edits to the same role record. It helps maintain data integrity by ensuring concurrent operations do not overwrite changes.
AspNetUserRoles Table
The columns in the AspNetUserRoles table generally include the following:
- UserId: The user ID from the AspNetUsers table. Part of the composite primary key. It corresponds to the Id column in the AspNetUsers table. It acts as a foreign key linking to the AspNetUsers table.
- RoleId: The role ID from the AspNetRoles table. Part of the composite primary key. It corresponds to the Id column in the AspNetRoles table. It acts as a foreign key linking to the AspNetRoles table.
AspNetUserClaims Table
The columns in the AspNetUserClaims table generally include the following:
- Id: The primary key for the user claim. It is an integer.
- UserId: The ID of the user associated with this claim. It acts as a foreign key linking to the Id column in the AspNetUsers table.
- ClaimType: The type of the claim (e.g., “birthdate”).
- ClaimValue: The value of the claim (e.g., “1980-01-01”).
AspNetUserLogins Table
The columns in the AspNetUserLogins table generally include the following:
- LoginProvider: This column stores the name of the external authentication provider (e.g., Google, Facebook, Microsoft, etc.). It is part of the composite primary key for the table.
- ProviderKey: This column stores the unique identifier from the login provider for the user. For instance, when a user logs in using Google, this field will store the unique ID assigned to the user by Google. It is also part of the composite primary key.
- ProviderDisplayName: This optional column can store the login provider's display name (e.g., “Google” instead of “google.com”). It is mainly used for display purposes in the UI.
- UserId: The local user ID that is linked to this login. It acts as a foreign key linking to the Id column in the AAspNetUsers table. It identifies the user associated with the particular login.
AspNetUserTokens Table
The columns in the AspNetUserTokens table generally include the following:
- UserId: The user ID from the AspNetUsers table. Part of the composite primary key.
- LoginProvider: This column specifies the name of the provider that generated the token. For instance, it could be an internal provider (like a password reset or email confirmation system) or an external authentication provider (like Google, Facebook, etc.) if the token is related to external authentication. It is part of the composite primary key.
- Name: This column stores the name of the token, such as an email confirmation token, password reset token, or two-factor authentication token. It could be something like PasswordReset, EmailConfirmation, AccessToken, etc. It is also part of the composite primary key.
- Value: The value of the token.
AspNetRoleClaims Table
The columns in the AspNetRoleClaims table generally include the following:
- Id: This is the primary key of the table.
- RoleId: The role ID associated with this claim. A foreign key that links to the Id column in the AspNetRoles table.
- ClaimType: This column stores the type of the claim, such as Permission, AccessLevel, or any other type that makes sense in the context of your application.
- ClaimValue: The actual value of the claim. For example, if the claim type is Permission, the claim value might be Edit_User or View_Reports, etc.
Customizing Database Core Identity Tables
By default, ASP.NET Core Identity provides a ready-to-use user and role system with basic properties like UserName, Email, and PasswordHash. However, most real-world applications need more information and custom features such as:
- Additional user details (first name, last name, dob, etc...
- Extended role information (description, active status)
- Relationships with other entities (such as linking address to a user
- Customized table names and schema to fit your project or organization's standards
How to Customize ASP.NET Core Identity Tables?
- Create a Custom User Class: Inherit from IdentityUser (or IdentityUser<TKey>) and add your properties (e.g., FirstName, LastName)
- Create a Custom Role Class: Inherit from IdentityRole (or IdentityRole<TKey>) and add your properties (e.g., Description)
-
Extending IdentityUser
using Microsoft.AspNetCore.Identity; namespace ASPNETCoreIdentityDemo.Models { public class ApplicationUser : IdentityUser { // Extended properties public string? FirstName { get; set; } public string? LastName { get; set; } } } -
Extending IdentityRole
Similarly, the IdentityRole is extended by creating ApplicationRole derived from IdentityRole<Guid>. You add properties like:
- Description - to describe the role's purpose.
using Microsoft.AspNetCore.Identity; namespace ASPNETCoreIdentityDemo.Models { public class ApplicationRole : IdentityRole { // Extended property public string? Description { get; set; } } } -
Configuring ApplicationDbContext
Now, we need to customize the ApplicationDbContext class to:
- Inherit from IdentityDbContext<ApplicationUser, ApplicationRole, Guid>, indicating the use of your custom user and role classes with Guid keys.
- Rename the default Identity tables to your preferred names (Users, Roles, UserRoles, etc.) using Fluent API in OnModelCreating. This helps maintain a clean and consistent database naming.
- Seed initial roles with fixed GUIDs and additional metadata using HasData for automatic insertion during migration.
So, please modify the ApplicationDbContext class as follows:
using ASPNETCoreIdentityDemo.Models; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace ASPNETCoreIdentityDemo.Data { public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<IdentityUserRole<string>>() .HasOne<ApplicationRole>() .WithMany() .HasForeignKey(ur => ur.RoleId) .OnDelete(DeleteBehavior.NoAction); } } } -
Make Changes in Program.cs
Change IdentityUser to ApplicationUser, and change IdentityRole to Application Role
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); builder.Services.AddIdentity<ApplicationUser, ApplicationRole>() .AddEntityFrameworkStores<ApplicationDbContext>(); -
Make changes to the database by entering the following into the Package Manager Console (Tools>NuGet Package Manager>Package Manager Console)
PM> Add-Migration IdentityMigration2 ... PM> update-database