$npx -y skills add wshaddix/dotnet-skills --skill asp-net-core-identity-patternsProduction-grade patterns for ASP.NET Core Identity in Razor Pages and web apps. Covers setup, customization, security hardening, auth flows, roles/claims, external providers, and integration best practices for .NET 8+ / .NET 9+. Use when implementing authentication and authoriza
| 1 | You are a senior .NET security & identity architect. When the task involves user authentication, registration, login, roles, claims, 2FA, external logins, or authorization in ASP.NET Core (especially Razor Pages), strictly follow these patterns. Prioritize OWASP compliance, least privilege, observability, and minimal attack surface. Target .NET 8+ with nullable enabled. |
| 2 | |
| 3 | ## Rationale |
| 4 | |
| 5 | ASP.NET Core Identity provides robust membership (users, roles, claims, tokens) but defaults are developer-friendly, not production-hardened. Misconfigurations lead to weak passwords, session hijacking, enumeration attacks, or compliance failures (GDPR, SOC2). These patterns enforce secure defaults, proper flows, and testable integration. |
| 6 | |
| 7 | ## Core Setup (Program.cs / Startup) |
| 8 | |
| 9 | - Use `AddDefaultIdentity<IdentityUser>()` or `AddIdentity<IdentityUser, IdentityRole>()` for role support. |
| 10 | - Chain with `AddEntityFrameworkStores<ApplicationDbContext>()`. |
| 11 | - Always configure options early: |
| 12 | |
| 13 | ```csharp |
| 14 | builder.Services.AddDefaultIdentity<IdentityUser>(options => |
| 15 | { |
| 16 | // Password policy - enforce strong defaults |
| 17 | options.Password.RequiredLength = 12; |
| 18 | options.Password.RequireDigit = true; |
| 19 | options.Password.RequireLowercase = true; |
| 20 | options.Password.RequireUppercase = true; |
| 21 | options.Password.RequireNonAlphanumeric = true; |
| 22 | options.Password.RequiredUniqueChars = 6; |
| 23 | |
| 24 | // Lockout - prevent brute force |
| 25 | options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15); |
| 26 | options.Lockout.MaxFailedAccessAttempts = 5; |
| 27 | options.Lockout.AllowedForNewUsers = true; |
| 28 | |
| 29 | // Sign-in requirements |
| 30 | options.SignIn.RequireConfirmedAccount = true; // Email confirmation mandatory |
| 31 | options.SignIn.RequireConfirmedEmail = true; |
| 32 | options.SignIn.RequireConfirmedPhoneNumber = false; // Optional 2FA/SMS |
| 33 | |
| 34 | // User settings |
| 35 | options.User.RequireUniqueEmail = true; |
| 36 | options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; |
| 37 | }) |
| 38 | .AddEntityFrameworkStores<ApplicationDbContext>() |
| 39 | .AddDefaultTokenProviders(); // For password reset, email confirmation, 2FA |
| 40 | ``` |
| 41 | |
| 42 | - Add authentication & authorization middleware **after** `UseRouting()`: |
| 43 | |
| 44 | ```csharp |
| 45 | app.UseAuthentication(); |
| 46 | app.UseAuthorization(); |
| 47 | app.MapRazorPages(); |
| 48 | ``` |
| 49 | |
| 50 | - Enable HTTPS enforcement globally: |
| 51 | |
| 52 | ```csharp |
| 53 | app.UseHsts(); |
| 54 | app.UseHttpsRedirection(); |
| 55 | ``` |
| 56 | |
| 57 | ## Razor Pages Integration |
| 58 | |
| 59 | - Scaffold Identity UI selectively: |
| 60 | |
| 61 | ```bash |
| 62 | dotnet aspnet-codegenerator identity -dc ApplicationDbContext \ |
| 63 | --files "Account.Register;Account.Login;Account.Logout;Account.ForgotPassword;Account.ConfirmEmail;Account.Manage.Index;Account.Manage.ChangePassword;Account.Manage.TwoFactorAuthentication" |
| 64 | ``` |
| 65 | |
| 66 | - Inject services in PageModels: |
| 67 | |
| 68 | ```csharp |
| 69 | private readonly UserManager<IdentityUser> _userManager; |
| 70 | private readonly SignInManager<IdentityUser> _signInManager; |
| 71 | private readonly ILogger<RegisterModel> _logger; |
| 72 | |
| 73 | public RegisterModel( |
| 74 | UserManager<IdentityUser> userManager, |
| 75 | SignInManager<IdentityUser> signInManager, |
| 76 | ILogger<RegisterModel> logger) |
| 77 | { |
| 78 | _userManager = userManager; |
| 79 | _signInManager = signInManager; |
| 80 | _logger = logger; |
| 81 | } |
| 82 | ``` |
| 83 | |
| 84 | - Always validate anti-forgery in forms: `@Html.AntiForgeryToken()` and `[ValidateAntiForgeryToken]` on POST handlers. |
| 85 | - Use `[Authorize]` on protected PageModels: |
| 86 | |
| 87 | ```csharp |
| 88 | [Authorize(Policy = "RequireAdminRole")] |
| 89 | public class AdminModel : PageModel { ... } |
| 90 | ``` |
| 91 | |
| 92 | ## Authorization Patterns |
| 93 | |
| 94 | - Prefer **policy-based** over role-based where possible: |
| 95 | |
| 96 | ```csharp |
| 97 | builder.Services.AddAuthorization(options => |
| 98 | { |
| 99 | options.AddPolicy("RequireAdminRole", policy => |
| 100 | policy.RequireRole("Admin")); |
| 101 | |
| 102 | options.AddPolicy("CanEditContent", policy => |
| 103 | policy.RequireClaim("Permission", "EditContent") |
| 104 | .RequireAuthenticatedUser()); |
| 105 | }); |
| 106 | ``` |
| 107 | |
| 108 | - Apply via `[Authorize(Policy = "CanEditContent")]` or in `RequireAuthorization()` on endpoints. |
| 109 | |
| 110 | ## Security Hardening |
| 111 | |
| 112 | - Cookie configuration: |
| 113 | |
| 114 | ```csharp |
| 115 | builder.Services.ConfigureApplicationCookie(options => |
| 116 | { |
| 117 | options.Cookie.HttpOnly = true; |
| 118 | options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // HTTPS only |
| 119 | options.Cookie.SameSite = SameSiteMode.Strict; // Mitigate CSRF |
| 120 | options.ExpireTimeSpan = TimeSpan.FromDays(14); |
| 121 | options.SlidingExpiration = true; |
| 122 | options.LoginPath = "/Identity/Account/Login"; |
| 123 | options.AccessDeniedPath = "/Identity/Account/AccessDenied"; |
| 124 | }); |
| 125 | ``` |
| 126 | |
| 127 | - Enable 2FA by default for sensitive apps: Guide us |