ASP.NET Core Identity is the built-in membership system for user authentication, password management, role-based access control, and claims-based identity. It integrates with Entity Framework Core to persist users, roles, and claims in a database, and with ASP.NET Core’s cookie authentication system for stateful browser sessions. Setting it up correctly — particularly the database schema, the IdentityDbContext, and the startup configuration — is the foundation for all authentication and authorisation throughout the application.
Setup and Configuration
// dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
// ── 1. ApplicationUser — extend IdentityUser with custom properties ────────
public class ApplicationUser : IdentityUser
{
// Extra profile properties beyond the Identity defaults
[StringLength(100)]
public string? DisplayName { get; set; }
[StringLength(500)]
public string? Bio { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public bool IsActive { get; set; } = true;
}
// ── 2. AppDbContext — add Identity tables ─────────────────────────────────
public class AppDbContext : IdentityDbContext<ApplicationUser>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
// Your application tables
public DbSet<Post> Posts => Set<Post>();
public DbSet<Comment> Comments => Set<Comment>();
// Identity tables are added by IdentityDbContext automatically:
// AspNetUsers, AspNetRoles, AspNetUserRoles, AspNetUserClaims,
// AspNetRoleClaims, AspNetUserLogins, AspNetUserTokens
}
// ── 3. Register Identity in Program.cs ───────────────────────────────────
builder.Services
.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Password policy
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.MinimumLength = 8;
// Lockout policy — protect against brute force
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User policy
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedEmail = false; // true in production
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders(); // for email confirmation, password reset tokens
// ── 4. Cookie authentication middleware ───────────────────────────────────
builder.Services.ConfigureApplicationCookie(options =>
{
options.LoginPath = "/account/login";
options.LogoutPath = "/account/logout";
options.AccessDeniedPath = "/account/access-denied";
options.ExpireTimeSpan = TimeSpan.FromDays(14);
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
// ── 5. Middleware pipeline ────────────────────────────────────────────────
app.UseAuthentication(); // must be before UseAuthorization
app.UseAuthorization();
IdentityDbContext<ApplicationUser> automatically configures EF Core mappings for all Identity tables. Run dotnet ef migrations add AddIdentity --project src/BlogApp.Infrastructure --startup-project src/BlogApp.Api to generate the migration. The migration creates seven tables: AspNetUsers (user accounts), AspNetRoles, AspNetUserRoles (many-to-many junction), AspNetUserClaims (user-specific claims), AspNetRoleClaims (role-level claims), AspNetUserLogins (external OAuth logins), and AspNetUserTokens (email confirmation, password reset, 2FA tokens). Never modify these table structures manually — use the EF Core model to extend them.RoleManager.CreateAsync and UserManager.CreateAsync inside a scope created from IServiceScopeFactory. This ensures roles and the admin user exist in every fresh environment without requiring manual database intervention. Store the seed admin credentials in environment variables or User Secrets, never in committed code.options.SignIn.RequireConfirmedEmail = true should be enabled in production — without it, a user can register with any email address (including one they do not own) and gain access. Enable it, implement email confirmation (Lesson 4), and test the full registration flow including the confirmation link. In development, set it to false to avoid needing a real email service during local development; re-enable it before deploying to production.Identity Table Schema
| Table | Purpose | Key Columns |
|---|---|---|
| AspNetUsers | User accounts | Id, UserName, NormalizedEmail, PasswordHash, SecurityStamp |
| AspNetRoles | Role definitions | Id, Name, NormalizedName |
| AspNetUserRoles | User-role assignments | UserId, RoleId (composite PK) |
| AspNetUserClaims | User-specific claims | Id, UserId, ClaimType, ClaimValue |
| AspNetRoleClaims | Role-level claims | Id, RoleId, ClaimType, ClaimValue |
| AspNetUserLogins | External OAuth logins | LoginProvider, ProviderKey, UserId |
| AspNetUserTokens | Confirmation, reset, 2FA tokens | UserId, LoginProvider, Name, Value |
Common Mistakes
Mistake 1 — Using IdentityDbContext without ApplicationUser (cannot extend user properties)
❌ Wrong — using plain IdentityDbContext without a custom user class; cannot add profile properties.
✅ Correct — always create ApplicationUser : IdentityUser and use IdentityDbContext<ApplicationUser>.
Mistake 2 — Registering AddAuthentication before AddIdentity (cookie configuration ignored)
❌ Wrong — calling AddAuthentication separately conflicts with Identity’s authentication setup.
✅ Correct — AddIdentity registers cookie authentication internally; use ConfigureApplicationCookie to customise it.