ASP.NET Core  

ASP.NET Core Identity Unleashed: Complete Guide to Registration, Roles, 2FA & Security (Part 13 of 40)

Previous Article: Complete ASP.NET Core Security Guide with Refresh Tokens & Claims 

ASP.NET Core Identity Unleashed: Complete Guide to Registration, Roles, 2FA & Security (Part 13 of 40) - FreeLearning365.com


Module Sequence: Part 13 of 40 - Intermediate Level Core Development Series

Master ASP.NET Core Identity with complete examples. Learn user management, roles, claims, 2FA, social logins, and enterprise security patterns.

Table of Contents

  1. Introduction to ASP.NET Core Identity

  2. Identity Architecture & Components

  3. Setting Up Identity with EF Core

  4. User Registration & Email Confirmation

  5. Login & Logout Systems

  6. Role-Based Authorization

  7. Claims-Based Authorization

  8. Two-Factor Authentication (2FA)

  9. Social Logins (OAuth2)

  10. Password & Security Policies

  11. User Management & Administration

  12. Custom User Stores & Properties

  13. Identity with JWT Tokens

  14. Real-World E-Commerce Implementation

  15. Testing Identity Systems

  16. Performance Optimization

  17. Security Best Practices

  18. Troubleshooting & Debugging

  19. Migration Strategies

  20. Future of Identity

1. Introduction to ASP.NET Core Identity

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

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": "[email protected]",
    "Username": "[email protected]",
    "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 = "[email protected]",
                Email = "[email protected]",
                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 = "[email protected]",
                    FirstName = "John",
                    LastName = "Manager",
                    Password = "Manager123!",
                    Roles = new[] { "Manager", "User" }
                },
                new
                {
                    Email = "[email protected]",
                    FirstName = "Alice",
                    LastName = "User",
                    Password = "User123!",
                    Roles = new[] { "User" }
                },
                new
                {
                    Email = "[email protected]",
                    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.