ASP.NET Core Identity Setup — Users, Roles and Database

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();
Note: 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.
Tip: Seed admin roles and the first admin user in a hosted service that runs at startup (or in an EF Core data seeder). Use 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.
Warning: 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>.

❌ Wrong — calling AddAuthentication separately conflicts with Identity’s authentication setup.

✅ Correct — AddIdentity registers cookie authentication internally; use ConfigureApplicationCookie to customise it.

🧠 Test Yourself

What does SecurityStamp in AspNetUsers do, and why is it important?