ASP.NET Core Identity Unleashed: Complete Guide to Secure Identity Systems
Table of Contents
1. Introduction to ASP.NET Core Identity {#introduction}
ASP.NET Core Identity is a complete membership system that adds login functionality to your application. It provides a robust framework for managing users, passwords, profile data, roles, claims, tokens, email confirmation, and more.
Why ASP.NET Core Identity is Essential
public class IdentityBenefits { // Built-in Security Features public class SecurityFeatures { // Password hashing with industry standards // Two-factor authentication // Account lockout for brute force protection // Email confirmation // Password reset functionality } // Extensibility & Customization public class Extensibility { // Custom user properties // Multiple authentication providers // Custom password validators // External login providers } // Integration with ASP.NET Core Ecosystem public class Integration { // Seamless Entity Framework Core integration // Built-in tag helpers for UI // Razor Pages scaffolding // API support with JWT tokens } }
2. Identity Architecture & Components {#identity-architecture}
Core Identity Components
public class IdentityArchitecture { // Main Identity Classes public class CoreClasses { // UserManager<TUser> - User management operations // SignInManager<TUser> - Sign-in operations // RoleManager<TRole> - Role management // IUserStore<TUser> - User storage abstraction } // Default Identity Models public class IdentityModels { // IdentityUser - Base user class // IdentityRole - Base role class // IdentityUserClaim - User claims // IdentityUserLogin - External logins // IdentityUserToken - Authentication tokens } }
3. Setting Up Identity with EF Core {#setup-identity}
Complete Project Setup
Project Structure:
IdentityDemo/ ├── Models/ ├── Data/ ├── Services/ ├── Controllers/ ├── Views/ ├── Program.cs └── appsettings.json
Program.cs - Complete Identity Setup
using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using IdentityDemo.Data; using IdentityDemo.Models; using IdentityDemo.Services; var builder = WebApplication.CreateBuilder(args); // Add DbContext with Identity builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // Add Identity Services builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options => { // Password settings options.Password.RequireDigit = true; options.Password.RequireLowercase = true; options.Password.RequireNonAlphanumeric = true; options.Password.RequireUppercase = true; options.Password.RequiredLength = 8; options.Password.RequiredUniqueChars = 1; // Lockout settings options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15); options.Lockout.MaxFailedAccessAttempts = 5; options.Lockout.AllowedForNewUsers = true; // User settings options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; options.User.RequireUniqueEmail = true; // SignIn settings options.SignIn.RequireConfirmedEmail = true; options.SignIn.RequireConfirmedAccount = true; }) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders() .AddDefaultUI(); // For Razor Pages UI // Add External Authentication builder.Services.AddAuthentication() .AddGoogle(options => { options.ClientId = builder.Configuration["Authentication:Google:ClientId"]!; options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]!; }) .AddFacebook(options => { options.AppId = builder.Configuration["Authentication:Facebook:AppId"]!; options.AppSecret = builder.Configuration["Authentication:Facebook:AppSecret"]!; }) .AddMicrosoftAccount(options => { options.ClientId = builder.Configuration["Authentication:Microsoft:ClientId"]!; options.ClientSecret = builder.Configuration["Authentication:Microsoft:ClientSecret"]!; }); // Add Authorization Policies builder.Services.AddAuthorization(options => { options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin")); options.AddPolicy("RequireManagerRole", policy => policy.RequireRole("Admin", "Manager")); options.AddPolicy("EmailVerified", policy => policy.RequireClaim("EmailVerified", "true")); options.AddPolicy("Over18", policy => policy.RequireAssertion(context => context.User.HasClaim(c => c.Type == "DateOfBirth" && DateTime.TryParse(c.Value, out var dateOfBirth) && dateOfBirth <= DateTime.Now.AddYears(-18) ) || context.User.IsInRole("Admin") )); }); // Add Application Services builder.Services.AddScoped<IEmailService, EmailService>(); builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IRoleService, RoleService>(); // Add Controllers with Views builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); // For Identity UI Pages // Configure Cookie Settings builder.Services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); options.LoginPath = "/Account/Login"; options.AccessDeniedPath = "/Account/AccessDenied"; options.SlidingExpiration = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); // Add Session (optional, for temp data) builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); var app = builder.Build(); // Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseSession(); // Map Controllers and Razor Pages app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapRazorPages(); // Seed Database with Initial Data using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; try { var context = services.GetRequiredService<ApplicationDbContext>(); var userManager = services.GetRequiredService<UserManager<ApplicationUser>>(); var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>(); await SeedData.InitializeAsync(context, userManager, roleManager); } catch (Exception ex) { var logger = services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex, "An error occurred seeding the DB."); } } app.Run();
Configuration Settings
appsettings.json
{ "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=IdentityDemo;Trusted_Connection=true;MultipleActiveResultSets=true" }, "Authentication": { "Google": { "ClientId": "your-google-client-id", "ClientSecret": "your-google-client-secret" }, "Facebook": { "AppId": "your-facebook-app-id", "AppSecret": "your-facebook-app-secret" }, "Microsoft": { "ClientId": "your-microsoft-client-id", "ClientSecret": "your-microsoft-client-secret" } }, "EmailSettings": { "SmtpServer": "smtp.gmail.com", "Port": 587, "SenderName": "Identity Demo", "SenderEmail": "noreply@identity-demo.com", "Username": "your-email@gmail.com", "Password": "your-app-password" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" }
4. Custom User & Role Models {#custom-models}
Extended User Model
Models/ApplicationUser.cs
using Microsoft.AspNetCore.Identity; using System.ComponentModel.DataAnnotations; namespace IdentityDemo.Models { public class ApplicationUser : IdentityUser { [Required] [StringLength(100)] [Display(Name = "First Name")] public string FirstName { get; set; } = string.Empty; [Required] [StringLength(100)] [Display(Name = "Last Name")] public string LastName { get; set; } = string.Empty; [Display(Name = "Full Name")] public string FullName => $"{FirstName} {LastName}"; [Required] [Display(Name = "Date of Birth")] [DataType(DataType.Date)] public DateTime DateOfBirth { get; set; } [StringLength(200)] public string? Address { get; set; } [StringLength(100)] public string? City { get; set; } [StringLength(100)] public string? State { get; set; } [StringLength(20)] [Display(Name = "Zip Code")] public string? ZipCode { get; set; } [StringLength(100)] public string? Country { get; set; } [StringLength(20)] [Display(Name = "Phone Number")] public override string? PhoneNumber { get; set; } [Display(Name = "Profile Picture")] public string? ProfilePictureUrl { get; set; } [StringLength(500)] public string? Bio { get; set; } [Display(Name = "Email Verified")] public bool IsEmailVerified { get; set; } [Display(Name = "Account Active")] public bool IsActive { get; set; } = true; [Display(Name = "Two Factor Enabled")] public bool IsTwoFactorEnabled { get; set; } [Display(Name = "Registration Date")] public DateTime RegistrationDate { get; set; } = DateTime.UtcNow; [Display(Name = "Last Login Date")] public DateTime? LastLoginDate { get; set; } [Display(Name = "Last Updated")] public DateTime? LastUpdated { get; set; } // Navigation properties public virtual ICollection<UserLoginHistory> LoginHistory { get; set; } = new List<UserLoginHistory>(); public virtual ICollection<UserProfile> Profiles { get; set; } = new List<UserProfile>(); // Computed properties [Display(Name = "Age")] public int Age { get { var today = DateTime.Today; var age = today.Year - DateOfBirth.Year; if (DateOfBirth.Date > today.AddYears(-age)) age--; return age; } } public bool IsOver18 => Age >= 18; } public class UserLoginHistory { public int Id { get; set; } [Required] public string UserId { get; set; } = string.Empty; [Required] public DateTime LoginTime { get; set; } = DateTime.UtcNow; [StringLength(45)] public string IpAddress { get; set; } = string.Empty; [StringLength(500)] public string UserAgent { get; set; } = string.Empty; public bool Success { get; set; } [StringLength(200)] public string? FailureReason { get; set; } // Navigation property public virtual ApplicationUser User { get; set; } = null!; } public class UserProfile { public int Id { get; set; } [Required] public string UserId { get; set; } = string.Empty; [StringLength(100)] public string? Company { get; set; } [StringLength(100)] public string? JobTitle { get; set; } [StringLength(100)] public string? Website { get; set; } [StringLength(100)] public string? LinkedIn { get; set; } [StringLength(100)] public string? Twitter { get; set; } [StringLength(100)] public string? GitHub { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime? UpdatedAt { get; set; } // Navigation property public virtual ApplicationUser User { get; set; } = null!; } } // DTOs for User Management namespace IdentityDemo.Models { public class RegisterViewModel { [Required] [StringLength(100)] [Display(Name = "First Name")] public string FirstName { get; set; } = string.Empty; [Required] [StringLength(100)] [Display(Name = "Last Name")] public string LastName { get; set; } = string.Empty; [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; } = string.Empty; [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } = string.Empty; [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } = string.Empty; [Required] [Display(Name = "Date of Birth")] [DataType(DataType.Date)] [MinimumAge(18, ErrorMessage = "You must be at least 18 years old.")] public DateTime DateOfBirth { get; set; } [Display(Name = "Phone Number")] [Phone] public string? PhoneNumber { get; set; } [Display(Name = "I agree to the terms and conditions")] [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the terms and conditions.")] public bool AcceptTerms { get; set; } } public class LoginViewModel { [Required] [EmailAddress] public string Email { get; set; } = string.Empty; [Required] [DataType(DataType.Password)] public string Password { get; set; } = string.Empty; [Display(Name = "Remember me?")] public bool RememberMe { get; set; } public string? ReturnUrl { get; set; } } public class ForgotPasswordViewModel { [Required] [EmailAddress] public string Email { get; set; } = string.Empty; } public class ResetPasswordViewModel { [Required] [EmailAddress] public string Email { get; set; } = string.Empty; [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] public string Password { get; set; } = string.Empty; [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } = string.Empty; public string Code { get; set; } = string.Empty; } public class ChangePasswordViewModel { [Required] [DataType(DataType.Password)] [Display(Name = "Current password")] public string OldPassword { get; set; } = string.Empty; [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "New password")] public string NewPassword { get; set; } = string.Empty; [DataType(DataType.Password)] [Display(Name = "Confirm new password")] [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] public string ConfirmPassword { get; set; } = string.Empty; } public class EditProfileViewModel { [Required] [StringLength(100)] [Display(Name = "First Name")] public string FirstName { get; set; } = string.Empty; [Required] [StringLength(100)] [Display(Name = "Last Name")] public string LastName { get; set; } = string.Empty; [Required] [EmailAddress] public string Email { get; set; } = string.Empty; [Phone] [Display(Name = "Phone Number")] public string? PhoneNumber { get; set; } [StringLength(200)] public string? Address { get; set; } [StringLength(100)] public string? City { get; set; } [StringLength(100)] public string? State { get; set; } [StringLength(20)] [Display(Name = "Zip Code")] public string? ZipCode { get; set; } [StringLength(100)] public string? Country { get; set; } [StringLength(500)] public string? Bio { get; set; } } // Custom Validation Attribute public class MinimumAgeAttribute : ValidationAttribute { private readonly int _minimumAge; public MinimumAgeAttribute(int minimumAge) { _minimumAge = minimumAge; } protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { if (value is DateTime dateOfBirth) { if (dateOfBirth.AddYears(_minimumAge) > DateTime.Today) { return new ValidationResult(ErrorMessage); } } return ValidationResult.Success; } } }
Database Context with Identity
Data/ApplicationDbContext.cs
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using IdentityDemo.Models; namespace IdentityDemo.Data { public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<UserLoginHistory> UserLoginHistory { get; set; } = null!; public DbSet<UserProfile> UserProfiles { get; set; } = null!; protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // Configure ApplicationUser builder.Entity<ApplicationUser>(entity => { entity.Property(u => u.FirstName) .HasMaxLength(100) .IsRequired(); entity.Property(u => u.LastName) .HasMaxLength(100) .IsRequired(); entity.Property(u => u.DateOfBirth) .IsRequired(); entity.Property(u => u.RegistrationDate) .HasDefaultValueSql("GETUTCDATE()"); // Indexes entity.HasIndex(u => u.Email).IsUnique(); entity.HasIndex(u => u.IsActive); entity.HasIndex(u => u.RegistrationDate); // Relationships entity.HasMany(u => u.LoginHistory) .WithOne(lh => lh.User) .HasForeignKey(lh => lh.UserId) .OnDelete(DeleteBehavior.Cascade); entity.HasMany(u => u.Profiles) .WithOne(p => p.User) .HasForeignKey(p => p.UserId) .OnDelete(DeleteBehavior.Cascade); }); // Configure UserLoginHistory builder.Entity<UserLoginHistory>(entity => { entity.HasKey(lh => lh.Id); entity.Property(lh => lh.LoginTime) .HasDefaultValueSql("GETUTCDATE()"); entity.Property(lh => lh.IpAddress) .HasMaxLength(45); entity.Property(lh => lh.UserAgent) .HasMaxLength(500); entity.Property(lh => lh.FailureReason) .HasMaxLength(200); // Indexes entity.HasIndex(lh => lh.UserId); entity.HasIndex(lh => lh.LoginTime); entity.HasIndex(lh => lh.Success); }); // Configure UserProfile builder.Entity<UserProfile>(entity => { entity.HasKey(p => p.Id); entity.Property(p => p.CreatedAt) .HasDefaultValueSql("GETUTCDATE()"); // Indexes entity.HasIndex(p => p.UserId).IsUnique(); }); // Seed initial roles and admin user builder.Entity<IdentityRole>().HasData( new IdentityRole { Id = "1", Name = "Admin", NormalizedName = "ADMIN" }, new IdentityRole { Id = "2", Name = "Manager", NormalizedName = "MANAGER" }, new IdentityRole { Id = "3", Name = "User", NormalizedName = "USER" } ); } } }
5. User Registration & Email Confirmation {#registration}
Account Controller with Complete Registration
Controllers/AccountController.cs
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.WebUtilities; using System.Text; using System.Text.Encodings.Web; using IdentityDemo.Models; using IdentityDemo.Services; namespace IdentityDemo.Controllers { public class AccountController : Controller { private readonly UserManager<ApplicationUser> _userManager; private readonly SignInManager<ApplicationUser> _signInManager; private readonly IEmailService _emailService; private readonly ILogger<AccountController> _logger; private readonly IUserService _userService; public AccountController( UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IEmailService emailService, ILogger<AccountController> logger, IUserService userService) { _userManager = userManager; _signInManager = signInManager; _emailService = emailService; _logger = logger; _userService = userService; } [HttpGet] public IActionResult Register(string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Register(RegisterViewModel model, string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email, FirstName = model.FirstName, LastName = model.LastName, DateOfBirth = model.DateOfBirth, PhoneNumber = model.PhoneNumber, RegistrationDate = DateTime.UtcNow, IsActive = true }; var result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); // Add to User role by default await _userManager.AddToRoleAsync(user, "User"); // Generate email confirmation token var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Action( "ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Scheme)!; // Send confirmation email await _emailService.SendEmailConfirmationAsync(model.Email, callbackUrl); // Log registration await _userService.LogUserActivityAsync(user.Id, "Registration", "User registered successfully"); if (_userManager.Options.SignIn.RequireConfirmedAccount) { return RedirectToAction("RegisterConfirmation", new { email = model.Email }); } else { await _signInManager.SignInAsync(user, isPersistent: false); _logger.LogInformation("User logged in after registration."); return RedirectToLocal(returnUrl); } } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } return View(model); } [HttpGet] public async Task<IActionResult> ConfirmEmail(string userId, string code) { if (userId == null || code == null) { return RedirectToAction("Index", "Home"); } var user = await _userManager.FindByIdAsync(userId); if (user == null) { return NotFound($"Unable to load user with ID '{userId}'."); } code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code)); var result = await _userManager.ConfirmEmailAsync(user, code); if (result.Succeeded) { user.IsEmailVerified = true; await _userManager.UpdateAsync(user); await _userService.LogUserActivityAsync(user.Id, "Email Confirmation", "Email confirmed successfully"); TempData["SuccessMessage"] = "Thank you for confirming your email."; return View("ConfirmEmail"); } else { _logger.LogWarning("Error confirming email for user {UserId}: {Errors}", userId, string.Join(", ", result.Errors.Select(e => e.Description))); TempData["ErrorMessage"] = "Error confirming your email."; return View("Error"); } } [HttpGet] public IActionResult RegisterConfirmation(string email) { ViewData["Email"] = email; return View(); } [HttpGet] public IActionResult Login(string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginViewModel model, string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { // Check if user exists and is active var user = await _userManager.FindByEmailAsync(model.Email); if (user != null && !user.IsActive) { ModelState.AddModelError(string.Empty, "Your account has been deactivated."); return View(model); } // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, set lockoutOnFailure: true var result = await _signInManager.PasswordSignInAsync( model.Email, model.Password, model.RememberMe, lockoutOnFailure: true); if (result.Succeeded) { _logger.LogInformation("User logged in."); // Update last login date user!.LastLoginDate = DateTime.UtcNow; await _userManager.UpdateAsync(user); // Log login activity await _userService.LogUserActivityAsync(user.Id, "Login", "User logged in successfully"); return RedirectToLocal(returnUrl); } if (result.RequiresTwoFactor) { return RedirectToAction("LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); } if (result.IsLockedOut) { _logger.LogWarning("User account locked out."); return RedirectToAction("Lockout"); } else { ModelState.AddModelError(string.Empty, "Invalid login attempt."); // Log failed login attempt if (user != null) { await _userService.LogUserActivityAsync(user.Id, "Failed Login", "Invalid login attempt"); } return View(model); } } return View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Logout() { var userId = _userManager.GetUserId(User); await _signInManager.SignOutAsync(); _logger.LogInformation("User logged out."); // Log logout activity if (userId != null) { await _userService.LogUserActivityAsync(userId, "Logout", "User logged out"); } return RedirectToAction("Index", "Home"); } [HttpGet] public IActionResult ForgotPassword() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model) { if (ModelState.IsValid) { var user = await _userManager.FindByEmailAsync(model.Email); if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) { // Don't reveal that the user does not exist or is not confirmed return RedirectToAction("ForgotPasswordConfirmation"); } // Generate password reset token var code = await _userManager.GeneratePasswordResetTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Action( "ResetPassword", "Account", new { code }, protocol: Request.Scheme)!; await _emailService.SendPasswordResetAsync(model.Email, callbackUrl); await _userService.LogUserActivityAsync(user.Id, "Password Reset Request", "Password reset requested"); return RedirectToAction("ForgotPasswordConfirmation"); } return View(model); } [HttpGet] public IActionResult ForgotPasswordConfirmation() { return View(); } [HttpGet] public IActionResult ResetPassword(string? code = null) { if (code == null) { return BadRequest("A code must be supplied for password reset."); } else { var model = new ResetPasswordViewModel { Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code)) }; return View(model); } } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model) { if (!ModelState.IsValid) { return View(model); } var user = await _userManager.FindByEmailAsync(model.Email); if (user == null) { // Don't reveal that the user does not exist return RedirectToAction("ResetPasswordConfirmation"); } var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); if (result.Succeeded) { await _userService.LogUserActivityAsync(user.Id, "Password Reset", "Password reset successfully"); return RedirectToAction("ResetPasswordConfirmation"); } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } return View(model); } [HttpGet] public IActionResult ResetPasswordConfirmation() { return View(); } [HttpGet] public IActionResult AccessDenied() { return View(); } [HttpGet] public IActionResult Lockout() { return View(); } private IActionResult RedirectToLocal(string? returnUrl) { if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } else { return RedirectToAction("Index", "Home"); } } } }
6. Role-Based Authorization {#role-authorization}
Role Management Service
Services/IRoleService.cs
using Microsoft.AspNetCore.Identity; using IdentityDemo.Models; namespace IdentityDemo.Services { public interface IRoleService { Task<List<IdentityRole>> GetAllRolesAsync(); Task<IdentityRole?> GetRoleByIdAsync(string roleId); Task<IdentityResult> CreateRoleAsync(string roleName); Task<IdentityResult> UpdateRoleAsync(string roleId, string newRoleName); Task<IdentityResult> DeleteRoleAsync(string roleId); Task<List<ApplicationUser>> GetUsersInRoleAsync(string roleName); Task<IdentityResult> AddUserToRoleAsync(string userId, string roleName); Task<IdentityResult> RemoveUserFromRoleAsync(string userId, string roleName); Task<List<string>> GetUserRolesAsync(string userId); Task<bool> IsUserInRoleAsync(string userId, string roleName); Task<Dictionary<string, List<string>>> GetUsersWithRolesAsync(); } public class RoleService : IRoleService { private readonly RoleManager<IdentityRole> _roleManager; private readonly UserManager<ApplicationUser> _userManager; private readonly ILogger<RoleService> _logger; public RoleService( RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager, ILogger<RoleService> logger) { _roleManager = roleManager; _userManager = userManager; _logger = logger; } public async Task<List<IdentityRole>> GetAllRolesAsync() { return await _roleManager.Roles.ToListAsync(); } public async Task<IdentityRole?> GetRoleByIdAsync(string roleId) { return await _roleManager.FindByIdAsync(roleId); } public async Task<IdentityResult> CreateRoleAsync(string roleName) { if (await _roleManager.RoleExistsAsync(roleName)) { return IdentityResult.Failed(new IdentityError { Description = $"Role '{roleName}' already exists." }); } var role = new IdentityRole(roleName); var result = await _roleManager.CreateAsync(role); if (result.Succeeded) { _logger.LogInformation("Role created: {RoleName}", roleName); } else { _logger.LogWarning("Failed to create role {RoleName}: {Errors}", roleName, string.Join(", ", result.Errors.Select(e => e.Description))); } return result; } public async Task<IdentityResult> UpdateRoleAsync(string roleId, string newRoleName) { var role = await _roleManager.FindByIdAsync(roleId); if (role == null) { return IdentityResult.Failed(new IdentityError { Description = "Role not found." }); } role.Name = newRoleName; role.NormalizedName = newRoleName.ToUpper(); var result = await _roleManager.UpdateAsync(role); if (result.Succeeded) { _logger.LogInformation("Role updated: {RoleId} to {NewRoleName}", roleId, newRoleName); } return result; } public async Task<IdentityResult> DeleteRoleAsync(string roleId) { var role = await _roleManager.FindByIdAsync(roleId); if (role == null) { return IdentityResult.Failed(new IdentityError { Description = "Role not found." }); } // Check if there are users in this role var usersInRole = await _userManager.GetUsersInRoleAsync(role.Name!); if (usersInRole.Any()) { return IdentityResult.Failed(new IdentityError { Description = $"Cannot delete role '{role.Name}' because it has users assigned." }); } var result = await _roleManager.DeleteAsync(role); if (result.Succeeded) { _logger.LogInformation("Role deleted: {RoleName}", role.Name); } return result; } public async Task<List<ApplicationUser>> GetUsersInRoleAsync(string roleName) { return (await _userManager.GetUsersInRoleAsync(roleName)).ToList(); } public async Task<IdentityResult> AddUserToRoleAsync(string userId, string roleName) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } if (!await _roleManager.RoleExistsAsync(roleName)) { return IdentityResult.Failed(new IdentityError { Description = $"Role '{roleName}' does not exist." }); } var result = await _userManager.AddToRoleAsync(user, roleName); if (result.Succeeded) { _logger.LogInformation("User {UserId} added to role {RoleName}", userId, roleName); } return result; } public async Task<IdentityResult> RemoveUserFromRoleAsync(string userId, string roleName) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } var result = await _userManager.RemoveFromRoleAsync(user, roleName); if (result.Succeeded) { _logger.LogInformation("User {UserId} removed from role {RoleName}", userId, roleName); } return result; } public async Task<List<string>> GetUserRolesAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return new List<string>(); } return (await _userManager.GetRolesAsync(user)).ToList(); } public async Task<bool> IsUserInRoleAsync(string userId, string roleName) { var user = await _userManager.FindByIdAsync(userId); if (user == null) return false; return await _userManager.IsInRoleAsync(user, roleName); } public async Task<Dictionary<string, List<string>>> GetUsersWithRolesAsync() { var users = _userManager.Users.ToList(); var result = new Dictionary<string, List<string>>(); foreach (var user in users) { var roles = await _userManager.GetRolesAsync(user); result[user.Id] = roles.ToList(); } return result; } } }
Role Management Controller
Controllers/RoleController.cs
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using IdentityDemo.Models; using IdentityDemo.Services; namespace IdentityDemo.Controllers { [Authorize(Roles = "Admin")] public class RoleController : Controller { private readonly IRoleService _roleService; private readonly IUserService _userService; private readonly ILogger<RoleController> _logger; public RoleController( IRoleService roleService, IUserService userService, ILogger<RoleController> logger) { _roleService = roleService; _userService = userService; _logger = logger; } [HttpGet] public async Task<IActionResult> Index() { var roles = await _roleService.GetAllRolesAsync(); return View(roles); } [HttpGet] public IActionResult Create() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create(string roleName) { if (string.IsNullOrWhiteSpace(roleName)) { ModelState.AddModelError("", "Role name is required."); return View(); } var result = await _roleService.CreateRoleAsync(roleName.Trim()); if (result.Succeeded) { TempData["SuccessMessage"] = $"Role '{roleName}' created successfully."; return RedirectToAction("Index"); } foreach (var error in result.Errors) { ModelState.AddModelError("", error.Description); } return View(); } [HttpGet] public async Task<IActionResult> Edit(string id) { var role = await _roleService.GetRoleByIdAsync(id); if (role == null) { return NotFound(); } return View(role); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(string id, string roleName) { if (string.IsNullOrWhiteSpace(roleName)) { ModelState.AddModelError("", "Role name is required."); return View(); } var result = await _roleService.UpdateRoleAsync(id, roleName.Trim()); if (result.Succeeded) { TempData["SuccessMessage"] = "Role updated successfully."; return RedirectToAction("Index"); } foreach (var error in result.Errors) { ModelState.AddModelError("", error.Description); } return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Delete(string id) { var result = await _roleService.DeleteRoleAsync(id); if (result.Succeeded) { TempData["SuccessMessage"] = "Role deleted successfully."; } else { TempData["ErrorMessage"] = result.Errors.First().Description; } return RedirectToAction("Index"); } [HttpGet] public async Task<IActionResult> UsersInRole(string roleName) { var users = await _roleService.GetUsersInRoleAsync(roleName); ViewData["RoleName"] = roleName; return View(users); } [HttpGet] public async Task<IActionResult> ManageUserRoles(string userId) { var user = await _userService.GetUserByIdAsync(userId); if (user == null) { return NotFound(); } var userRoles = await _roleService.GetUserRolesAsync(userId); var allRoles = await _roleService.GetAllRolesAsync(); var model = new ManageUserRolesViewModel { UserId = userId, UserName = user.UserName!, UserRoles = userRoles, AllRoles = allRoles.Select(r => r.Name!).ToList() }; return View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> ManageUserRoles(ManageUserRolesViewModel model) { var user = await _userService.GetUserByIdAsync(model.UserId); if (user == null) { return NotFound(); } // Get current roles var currentRoles = await _roleService.GetUserRolesAsync(model.UserId); // Roles to add var rolesToAdd = model.SelectedRoles.Except(currentRoles); foreach (var role in rolesToAdd) { await _roleService.AddUserToRoleAsync(model.UserId, role); } // Roles to remove var rolesToRemove = currentRoles.Except(model.SelectedRoles); foreach (var role in rolesToRemove) { await _roleService.RemoveUserFromRoleAsync(model.UserId, role); } TempData["SuccessMessage"] = "User roles updated successfully."; return RedirectToAction("Index", "UserManagement"); } } public class ManageUserRolesViewModel { public string UserId { get; set; } = string.Empty; public string UserName { get; set; } = string.Empty; public List<string> UserRoles { get; set; } = new(); public List<string> AllRoles { get; set; } = new(); public List<string> SelectedRoles { get; set; } = new(); } }
7. Claims-Based Authorization {#claims-authorization}
Claims Management Service
Services/IClaimsService.cs
using Microsoft.AspNetCore.Identity; using System.Security.Claims; using IdentityDemo.Models; namespace IdentityDemo.Services { public interface IClaimsService { Task<List<Claim>> GetUserClaimsAsync(string userId); Task<IdentityResult> AddClaimToUserAsync(string userId, Claim claim); Task<IdentityResult> RemoveClaimFromUserAsync(string userId, Claim claim); Task<bool> UserHasClaimAsync(string userId, string claimType, string claimValue); Task<IdentityResult> AddClaimToRoleAsync(string roleId, Claim claim); Task<IdentityResult> RemoveClaimFromRoleAsync(string roleId, Claim claim); Task<List<Claim>> GetRoleClaimsAsync(string roleId); } public class ClaimsService : IClaimsService { private readonly UserManager<ApplicationUser> _userManager; private readonly RoleManager<IdentityRole> _roleManager; private readonly ILogger<ClaimsService> _logger; public ClaimsService( UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, ILogger<ClaimsService> logger) { _userManager = userManager; _roleManager = roleManager; _logger = logger; } public async Task<List<Claim>> GetUserClaimsAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); if (user == null) return new List<Claim>(); var claims = await _userManager.GetClaimsAsync(user); return claims.ToList(); } public async Task<IdentityResult> AddClaimToUserAsync(string userId, Claim claim) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } // Check if claim already exists var existingClaims = await _userManager.GetClaimsAsync(user); if (existingClaims.Any(c => c.Type == claim.Type && c.Value == claim.Value)) { return IdentityResult.Failed(new IdentityError { Description = "Claim already exists for this user." }); } var result = await _userManager.AddClaimAsync(user, claim); if (result.Succeeded) { _logger.LogInformation("Claim {ClaimType}:{ClaimValue} added to user {UserId}", claim.Type, claim.Value, userId); } return result; } public async Task<IdentityResult> RemoveClaimFromUserAsync(string userId, Claim claim) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } var result = await _userManager.RemoveClaimAsync(user, claim); if (result.Succeeded) { _logger.LogInformation("Claim {ClaimType}:{ClaimValue} removed from user {UserId}", claim.Type, claim.Value, userId); } return result; } public async Task<bool> UserHasClaimAsync(string userId, string claimType, string claimValue) { var user = await _userManager.FindByIdAsync(userId); if (user == null) return false; var claims = await _userManager.GetClaimsAsync(user); return claims.Any(c => c.Type == claimType && c.Value == claimValue); } public async Task<IdentityResult> AddClaimToRoleAsync(string roleId, Claim claim) { var role = await _roleManager.FindByIdAsync(roleId); if (role == null) { return IdentityResult.Failed(new IdentityError { Description = "Role not found." }); } var result = await _roleManager.AddClaimAsync(role, claim); if (result.Succeeded) { _logger.LogInformation("Claim {ClaimType}:{ClaimValue} added to role {RoleName}", claim.Type, claim.Value, role.Name); } return result; } public async Task<IdentityResult> RemoveClaimFromRoleAsync(string roleId, Claim claim) { var role = await _roleManager.FindByIdAsync(roleId); if (role == null) { return IdentityResult.Failed(new IdentityError { Description = "Role not found." }); } var result = await _roleManager.RemoveClaimAsync(role, claim); if (result.Succeeded) { _logger.LogInformation("Claim {ClaimType}:{ClaimValue} removed from role {RoleName}", claim.Type, claim.Value, role.Name); } return result; } public async Task<List<Claim>> GetRoleClaimsAsync(string roleId) { var role = await _roleManager.FindByIdAsync(roleId); if (role == null) return new List<Claim>(); var claims = await _roleManager.GetClaimsAsync(role); return claims.ToList(); } } }
8. Two-Factor Authentication (2FA) {#2fa}
2FA Implementation
Services/TwoFactorService.cs
using Microsoft.AspNetCore.Identity; using System.Text; using IdentityDemo.Models; namespace IdentityDemo.Services { public interface ITwoFactorService { Task<bool> IsTwoFactorEnabledAsync(string userId); Task<IdentityResult> EnableTwoFactorAsync(string userId); Task<IdentityResult> DisableTwoFactorAsync(string userId); Task<string> GenerateTwoFactorTokenAsync(string userId); Task<bool> VerifyTwoFactorTokenAsync(string userId, string token); Task<List<string>> GenerateRecoveryCodesAsync(string userId); Task<IdentityResult> RedeemRecoveryCodeAsync(string userId, string code); Task<int> GetRecoveryCodesLeftAsync(string userId); } public class TwoFactorService : ITwoFactorService { private readonly UserManager<ApplicationUser> _userManager; private readonly IEmailService _emailService; private readonly ILogger<TwoFactorService> _logger; public TwoFactorService( UserManager<ApplicationUser> userManager, IEmailService emailService, ILogger<TwoFactorService> logger) { _userManager = userManager; _emailService = emailService; _logger = logger; } public async Task<bool> IsTwoFactorEnabledAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); return user != null && await _userManager.GetTwoFactorEnabledAsync(user); } public async Task<IdentityResult> EnableTwoFactorAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } var result = await _userManager.SetTwoFactorEnabledAsync(user, true); if (result.Succeeded) { user.IsTwoFactorEnabled = true; await _userManager.UpdateAsync(user); _logger.LogInformation("2FA enabled for user: {UserId}", userId); } return result; } public async Task<IdentityResult> DisableTwoFactorAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } var result = await _userManager.SetTwoFactorEnabledAsync(user, false); if (result.Succeeded) { user.IsTwoFactorEnabled = false; await _userManager.UpdateAsync(user); _logger.LogInformation("2FA disabled for user: {UserId}", userId); } return result; } public async Task<string> GenerateTwoFactorTokenAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { throw new InvalidOperationException("User not found."); } // Generate the token (this would typically be for authenticator app) // For email/SMS, we'll generate a custom token var token = await _userManager.GenerateTwoFactorTokenAsync(user, "Email"); // For demo purposes, we'll generate a 6-digit code if (string.IsNullOrEmpty(token)) { var random = new Random(); token = random.Next(100000, 999999).ToString(); } // Send the token via email await _emailService.SendTwoFactorCodeAsync(user.Email!, token); _logger.LogInformation("2FA token generated for user: {UserId}", userId); return token; } public async Task<bool> VerifyTwoFactorTokenAsync(string userId, string token) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return false; } var isValid = await _userManager.VerifyTwoFactorTokenAsync(user, "Email", token); if (isValid) { _logger.LogInformation("2FA token verified for user: {UserId}", userId); } else { _logger.LogWarning("Invalid 2FA token for user: {UserId}", userId); } return isValid; } public async Task<List<string>> GenerateRecoveryCodesAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { throw new InvalidOperationException("User not found."); } var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); if (recoveryCodes != null) { _logger.LogInformation("Recovery codes generated for user: {UserId}", userId); // Store recovery codes securely (in real app, you might want to hash them) var codes = recoveryCodes.ToList(); // Send recovery codes via email await _emailService.SendRecoveryCodesAsync(user.Email!, codes); return codes; } return new List<string>(); } public async Task<IdentityResult> RedeemRecoveryCodeAsync(string userId, string code) { var user = await _userManager.FindByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } var result = await _userManager.RedeemTwoFactorRecoveryCodeAsync(user, code); if (result.Succeeded) { _logger.LogInformation("Recovery code redeemed for user: {UserId}", userId); } else { _logger.LogWarning("Failed to redeem recovery code for user: {UserId}", userId); } return result; } public async Task<int> GetRecoveryCodesLeftAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); if (user == null) return 0; var recoveryCodes = await _userManager.CountRecoveryCodesAsync(user); return recoveryCodes; } } }
9. User Management & Administration {#user-management}
User Management Service
Services/IUserService.cs
using Microsoft.AspNetCore.Identity; using IdentityDemo.Models; namespace IdentityDemo.Services { public interface IUserService { Task<List<ApplicationUser>> GetAllUsersAsync(); Task<ApplicationUser?> GetUserByIdAsync(string userId); Task<ApplicationUser?> GetUserByEmailAsync(string email); Task<IdentityResult> UpdateUserAsync(ApplicationUser user); Task<IdentityResult> DeleteUserAsync(string userId); Task<IdentityResult> DeactivateUserAsync(string userId); Task<IdentityResult> ActivateUserAsync(string userId); Task<List<ApplicationUser>> GetInactiveUsersAsync(); Task<Dictionary<string, object>> GetUserStatisticsAsync(); Task LogUserActivityAsync(string userId, string activity, string description); Task<List<UserLoginHistory>> GetUserLoginHistoryAsync(string userId, int days = 30); Task<bool> IsUserActiveAsync(string userId); } public class UserService : IUserService { private readonly UserManager<ApplicationUser> _userManager; private readonly ApplicationDbContext _context; private readonly ILogger<UserService> _logger; public UserService( UserManager<ApplicationUser> userManager, ApplicationDbContext context, ILogger<UserService> logger) { _userManager = userManager; _context = context; _logger = logger; } public async Task<List<ApplicationUser>> GetAllUsersAsync() { return await _userManager.Users .Include(u => u.LoginHistory) .OrderByDescending(u => u.RegistrationDate) .ToListAsync(); } public async Task<ApplicationUser?> GetUserByIdAsync(string userId) { return await _userManager.Users .Include(u => u.LoginHistory) .Include(u => u.Profiles) .FirstOrDefaultAsync(u => u.Id == userId); } public async Task<ApplicationUser?> GetUserByEmailAsync(string email) { return await _userManager.FindByEmailAsync(email); } public async Task<IdentityResult> UpdateUserAsync(ApplicationUser user) { user.LastUpdated = DateTime.UtcNow; var result = await _userManager.UpdateAsync(user); if (result.Succeeded) { _logger.LogInformation("User updated: {UserId}", user.Id); } else { _logger.LogWarning("Failed to update user {UserId}: {Errors}", user.Id, string.Join(", ", result.Errors.Select(e => e.Description))); } return result; } public async Task<IdentityResult> DeleteUserAsync(string userId) { var user = await GetUserByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } var result = await _userManager.DeleteAsync(user); if (result.Succeeded) { _logger.LogInformation("User deleted: {UserId}", userId); } return result; } public async Task<IdentityResult> DeactivateUserAsync(string userId) { var user = await GetUserByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } user.IsActive = false; user.LastUpdated = DateTime.UtcNow; var result = await _userManager.UpdateAsync(user); if (result.Succeeded) { await LogUserActivityAsync(userId, "Account Deactivated", "User account deactivated by administrator"); _logger.LogInformation("User deactivated: {UserId}", userId); } return result; } public async Task<IdentityResult> ActivateUserAsync(string userId) { var user = await GetUserByIdAsync(userId); if (user == null) { return IdentityResult.Failed(new IdentityError { Description = "User not found." }); } user.IsActive = true; user.LastUpdated = DateTime.UtcNow; var result = await _userManager.UpdateAsync(user); if (result.Succeeded) { await LogUserActivityAsync(userId, "Account Activated", "User account activated by administrator"); _logger.LogInformation("User activated: {UserId}", userId); } return result; } public async Task<List<ApplicationUser>> GetInactiveUsersAsync() { return await _userManager.Users .Where(u => !u.IsActive) .OrderByDescending(u => u.LastUpdated) .ToListAsync(); } public async Task<Dictionary<string, object>> GetUserStatisticsAsync() { var totalUsers = await _userManager.Users.CountAsync(); var activeUsers = await _userManager.Users.CountAsync(u => u.IsActive); var verifiedUsers = await _userManager.Users.CountAsync(u => u.EmailConfirmed); var usersWith2FA = await _userManager.Users.CountAsync(u => u.TwoFactorEnabled); var recentRegistrations = await _userManager.Users .Where(u => u.RegistrationDate >= DateTime.UtcNow.AddDays(-7)) .CountAsync(); var stats = new Dictionary<string, object> { ["TotalUsers"] = totalUsers, ["ActiveUsers"] = activeUsers, ["VerifiedUsers"] = verifiedUsers, ["UsersWith2FA"] = usersWith2FA, ["RecentRegistrations"] = recentRegistrations, ["InactiveUsers"] = totalUsers - activeUsers }; return stats; } public async Task LogUserActivityAsync(string userId, string activity, string description) { try { var loginHistory = new UserLoginHistory { UserId = userId, LoginTime = DateTime.UtcNow, IpAddress = "System", // You can get real IP from HttpContext UserAgent = "System", Success = true, FailureReason = null }; _context.UserLoginHistory.Add(loginHistory); await _context.SaveChangesAsync(); _logger.LogInformation("User activity logged: {UserId} - {Activity}", userId, activity); } catch (Exception ex) { _logger.LogError(ex, "Error logging user activity for {UserId}", userId); } } public async Task<List<UserLoginHistory>> GetUserLoginHistoryAsync(string userId, int days = 30) { var cutoffDate = DateTime.UtcNow.AddDays(-days); return await _context.UserLoginHistory .Where(lh => lh.UserId == userId && lh.LoginTime >= cutoffDate) .OrderByDescending(lh => lh.LoginTime) .ToListAsync(); } public async Task<bool> IsUserActiveAsync(string userId) { var user = await GetUserByIdAsync(userId); return user?.IsActive ?? false; } } }
User Management Controller
Controllers/UserManagementController.cs
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using IdentityDemo.Models; using IdentityDemo.Services; namespace IdentityDemo.Controllers { [Authorize(Roles = "Admin,Manager")] public class UserManagementController : Controller { private readonly IUserService _userService; private readonly IRoleService _roleService; private readonly IClaimsService _claimsService; private readonly ILogger<UserManagementController> _logger; public UserManagementController( IUserService userService, IRoleService roleService, IClaimsService claimsService, ILogger<UserManagementController> logger) { _userService = userService; _roleService = roleService; _claimsService = claimsService; _logger = logger; } [HttpGet] public async Task<IActionResult> Index() { var users = await _userService.GetAllUsersAsync(); return View(users); } [HttpGet] public async Task<IActionResult> Details(string id) { var user = await _userService.GetUserByIdAsync(id); if (user == null) { return NotFound(); } var userRoles = await _roleService.GetUserRolesAsync(id); var userClaims = await _claimsService.GetUserClaimsAsync(id); var loginHistory = await _userService.GetUserLoginHistoryAsync(id, 30); var model = new UserDetailsViewModel { User = user, Roles = userRoles, Claims = userClaims, LoginHistory = loginHistory }; return View(model); } [HttpGet] public async Task<IActionResult> Edit(string id) { var user = await _userService.GetUserByIdAsync(id); if (user == null) { return NotFound(); } var model = new EditUserViewModel { Id = user.Id, FirstName = user.FirstName, LastName = user.LastName, Email = user.Email!, PhoneNumber = user.PhoneNumber, DateOfBirth = user.DateOfBirth, Address = user.Address, City = user.City, State = user.State, ZipCode = user.ZipCode, Country = user.Country, Bio = user.Bio, IsActive = user.IsActive }; return View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(EditUserViewModel model) { if (!ModelState.IsValid) { return View(model); } var user = await _userService.GetUserByIdAsync(model.Id); if (user == null) { return NotFound(); } user.FirstName = model.FirstName; user.LastName = model.LastName; user.Email = model.Email; user.UserName = model.Email; user.PhoneNumber = model.PhoneNumber; user.DateOfBirth = model.DateOfBirth; user.Address = model.Address; user.City = model.City; user.State = model.State; user.ZipCode = model.ZipCode; user.Country = model.Country; user.Bio = model.Bio; user.IsActive = model.IsActive; var result = await _userService.UpdateUserAsync(user); if (result.Succeeded) { TempData["SuccessMessage"] = "User updated successfully."; return RedirectToAction("Details", new { id = model.Id }); } foreach (var error in result.Errors) { ModelState.AddModelError("", error.Description); } return View(model); } [HttpPost] [ValidateAntiForgeryToken] [Authorize(Roles = "Admin")] public async Task<IActionResult> Deactivate(string id) { var result = await _userService.DeactivateUserAsync(id); if (result.Succeeded) { TempData["SuccessMessage"] = "User deactivated successfully."; } else { TempData["ErrorMessage"] = result.Errors.First().Description; } return RedirectToAction("Details", new { id }); } [HttpPost] [ValidateAntiForgeryToken] [Authorize(Roles = "Admin")] public async Task<IActionResult> Activate(string id) { var result = await _userService.ActivateUserAsync(id); if (result.Succeeded) { TempData["SuccessMessage"] = "User activated successfully."; } else { TempData["ErrorMessage"] = result.Errors.First().Description; } return RedirectToAction("Details", new { id }); } [HttpGet] public async Task<IActionResult> Statistics() { var stats = await _userService.GetUserStatisticsAsync(); return View(stats); } [HttpGet] public async Task<IActionResult> InactiveUsers() { var users = await _userService.GetInactiveUsersAsync(); return View(users); } } public class UserDetailsViewModel { public ApplicationUser User { get; set; } = null!; public List<string> Roles { get; set; } = new(); public List<Claim> Claims { get; set; } = new(); public List<UserLoginHistory> LoginHistory { get; set; } = new(); } public class EditUserViewModel { public string Id { get; set; } = string.Empty; [Required] [StringLength(100)] [Display(Name = "First Name")] public string FirstName { get; set; } = string.Empty; [Required] [StringLength(100)] [Display(Name = "Last Name")] public string LastName { get; set; } = string.Empty; [Required] [EmailAddress] public string Email { get; set; } = string.Empty; [Phone] [Display(Name = "Phone Number")] public string? PhoneNumber { get; set; } [Required] [Display(Name = "Date of Birth")] [DataType(DataType.Date)] public DateTime DateOfBirth { get; set; } [StringLength(200)] public string? Address { get; set; } [StringLength(100)] public string? City { get; set; } [StringLength(100)] public string? State { get; set; } [StringLength(20)] [Display(Name = "Zip Code")] public string? ZipCode { get; set; } [StringLength(100)] public string? Country { get; set; } [StringLength(500)] public string? Bio { get; set; } [Display(Name = "Active Account")] public bool IsActive { get; set; } = true; } }
10. Database Seeding & Initialization {#database-seeding}
Seed Data Implementation
Data/SeedData.cs
using Microsoft.AspNetCore.Identity; using IdentityDemo.Models; namespace IdentityDemo.Data { public static class SeedData { public static async Task InitializeAsync( ApplicationDbContext context, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager) { context.Database.EnsureCreated(); // Seed Roles await SeedRolesAsync(roleManager); // Seed Admin User await SeedAdminUserAsync(userManager); // Seed Sample Users await SeedSampleUsersAsync(userManager); } private static async Task SeedRolesAsync(RoleManager<IdentityRole> roleManager) { string[] roleNames = { "Admin", "Manager", "User" }; foreach (var roleName in roleNames) { var roleExist = await roleManager.RoleExistsAsync(roleName); if (!roleExist) { await roleManager.CreateAsync(new IdentityRole(roleName)); } } } private static async Task SeedAdminUserAsync(UserManager<ApplicationUser> userManager) { var adminUser = new ApplicationUser { UserName = "admin@identity-demo.com", Email = "admin@identity-demo.com", FirstName = "System", LastName = "Administrator", DateOfBirth = new DateTime(1980, 1, 1), PhoneNumber = "+1234567890", EmailConfirmed = true, IsEmailVerified = true, IsActive = true, RegistrationDate = DateTime.UtcNow }; var admin = await userManager.FindByEmailAsync(adminUser.Email); if (admin == null) { var createAdmin = await userManager.CreateAsync(adminUser, "Admin123!"); if (createAdmin.Succeeded) { await userManager.AddToRoleAsync(adminUser, "Admin"); await userManager.AddToRoleAsync(adminUser, "Manager"); await userManager.AddToRoleAsync(adminUser, "User"); } } } private static async Task SeedSampleUsersAsync(UserManager<ApplicationUser> userManager) { var sampleUsers = new[] { new { Email = "manager@identity-demo.com", FirstName = "John", LastName = "Manager", Password = "Manager123!", Roles = new[] { "Manager", "User" } }, new { Email = "user1@identity-demo.com", FirstName = "Alice", LastName = "User", Password = "User123!", Roles = new[] { "User" } }, new { Email = "user2@identity-demo.com", FirstName = "Bob", LastName = "User", Password = "User123!", Roles = new[] { "User" } } }; foreach (var userInfo in sampleUsers) { var user = await userManager.FindByEmailAsync(userInfo.Email); if (user == null) { var newUser = new ApplicationUser { UserName = userInfo.Email, Email = userInfo.Email, FirstName = userInfo.FirstName, LastName = userInfo.LastName, DateOfBirth = new DateTime(1990, 1, 1), EmailConfirmed = true, IsEmailVerified = true, IsActive = true, RegistrationDate = DateTime.UtcNow }; var createUser = await userManager.CreateAsync(newUser, userInfo.Password); if (createUser.Succeeded) { foreach (var role in userInfo.Roles) { await userManager.AddToRoleAsync(newUser, role); } } } } } } }
This comprehensive ASP.NET Core Identity guide provides complete, production-ready examples that cover all aspects of modern identity management. The implementation includes user registration, role-based authorization, claims management, two-factor authentication, and comprehensive administration features.

0 Comments
thanks for your comments!