ASP.NET Core  

JWT Authentication Ninja: Complete ASP.NET Core Security Guide with Refresh Tokens & Claims (Part-12 of 40)

JWT Authentication Ninja: Complete ASP.NET Core Security Guide with Refresh Tokens & Claims (Part-12 of 40) - FreeLearning365.com

Table of Contents

  1. Introduction to JWT Authentication

  2. JWT Structure & Security Fundamentals

  3. Setting Up JWT Authentication

  4. User Registration & Login System

  5. Token Generation & Validation

  6. Claims & Role-Based Authorization

  7. Refresh Token Strategies

  8. Password Security & Hashing

  9. Advanced Security Middleware

  10. Two-Factor Authentication (2FA)

  11. Rate Limiting & Brute Force Protection

  12. Token Blacklisting & Revocation

  13. API Security Best Practices

  14. Real-World E-Commerce Implementation

  15. Testing Security Implementation

  16. Production Deployment & Hardening

  17. Monitoring & Security Auditing

  18. Common Vulnerabilities & Protection

  19. Performance & Scalability

  20. Future Security Trends

1. Introduction to JWT Authentication {#introduction}

JWT (JSON Web Token) has become the industry standard for securing modern web applications and APIs. Unlike traditional session-based authentication, JWT provides a stateless, scalable approach to user authentication and authorization.

Why JWT is Essential for Modern Applications

  
    public class JWTBenefits
{
    // Stateless - No server-side sessions
    public class StatelessExample
    {
        // Traditional Sessions: Server stores session data
        // JWT: All data stored in token, verified by signature
    }
    
    // Scalability - Easy to scale horizontally
    public class ScalabilityExample
    {
        // No session affinity required
        // Any server can validate tokens
    }
    
    // Mobile & SPA Friendly
    public class MobileFriendlyExample
    {
        // Works seamlessly with mobile apps
        // Perfect for Single Page Applications
    }
    
    // Microservices Ready
    public class MicroservicesExample
    {
        // Tokens can be passed between services
        // Centralized authentication
    }
}
  

2. JWT Structure & Security Fundamentals {#jwt-fundamentals}

Understanding JWT Components

  
    public class JWTStructure
{
    // JWT has three parts: Header.Payload.Signature
    public class TokenComponents
    {
        // Header: Algorithm & token type
        // Payload: Claims (user data)
        // Signature: Verification
    }
    
    // Example JWT Token
    public class ExampleToken
    {
        // Header
        /*
        {
          "alg": "HS256",
          "typ": "JWT"
        }
        */
        
        // Payload
        /*
        {
          "sub": "1234567890",
          "name": "John Doe",
          "iat": 1516239022,
          "exp": 1516242622,
          "roles": ["User", "Admin"]
        }
        */
        
        // Signature
        // HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
    }
}
  

3. Setting Up JWT Authentication {#setup-jwt}

Complete Project Structure

  
    JWTAuthDemo/
├── Models/
├── Services/
├── Controllers/
├── Middleware/
├── Helpers/
├── Program.cs
└── appsettings.json
  

Program.cs - Complete JWT Setup

  
    using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.EntityFrameworkCore;
using JWTAuthDemo.Data;
using JWTAuthDemo.Services;
using JWTAuthDemo.Middleware;

var builder = WebApplication.CreateBuilder(args);

// Add Database Context
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Configure JWT Settings
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var secretKey = Encoding.ASCII.GetBytes(jwtSettings["SecretKey"]!);

// Add Authentication
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.RequireHttpsMetadata = false; // Set to true in production
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(secretKey),
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidIssuer = jwtSettings["Issuer"],
        ValidAudience = jwtSettings["Audience"],
        ClockSkew = TimeSpan.Zero // Remove delay of token when expire
    };

    // Events for custom token handling
    options.Events = new JwtBearerEvents
    {
        OnAuthenticationFailed = context =>
        {
            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
            {
                context.Response.Headers.Add("Token-Expired", "true");
            }
            return Task.CompletedTask;
        },
        OnTokenValidated = context =>
        {
            // Custom validation logic
            return Task.CompletedTask;
        }
    };
});

// Add Authorization with Policies
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireAdminRole", policy => 
        policy.RequireRole("Admin"));
    
    options.AddPolicy("RequireUserRole", policy => 
        policy.RequireRole("User", "Admin"));
    
    options.AddPolicy("Over18", policy => 
        policy.RequireClaim("DateOfBirth", DateTime.Now.AddYears(-18).ToString("yyyy-MM-dd")));
    
    options.AddPolicy("EmailVerified", policy => 
        policy.RequireClaim("EmailVerified", "true"));
});

// Add Services
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();
builder.Services.AddScoped<IEmailService, EmailService>();

// Add Controllers
builder.Services.AddControllers();

// Add Swagger with JWT Support
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new() { Title = "JWT Auth API", Version = "v1" });
    
    // Add JWT Authentication to Swagger
    c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
    {
        Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
        Name = "Authorization",
        In = Microsoft.OpenApi.Models.ParameterLocation.Header,
        Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });
    
    c.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
    {
        {
            new Microsoft.OpenApi.Models.OpenApiSecurityScheme
            {
                Reference = new Microsoft.OpenApi.Models.OpenApiReference
                {
                    Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});

var app = builder.Build();

// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
    app.UseDeveloperExceptionPage();
}

app.UseHttpsRedirection();

// Custom Security Middleware
app.UseSecurityHeaders();
app.UseRateLimitingMiddleware();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();
  

Configuration Settings

appsettings.json

  
    {
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=JWTAuthDemo;Trusted_Connection=true;MultipleActiveResultSets=true"
  },
  "JwtSettings": {
    "SecretKey": "YourSuperSecretKeyThatIsAtLeast32CharactersLong!",
    "Issuer": "JWTAuthDemo",
    "Audience": "JWTAuthDemoUsers",
    "AccessTokenExpirationMinutes": 15,
    "RefreshTokenExpirationDays": 7
  },
  "EmailSettings": {
    "SmtpServer": "smtp.gmail.com",
    "Port": 587,
    "SenderName": "JWT Auth Demo",
    "SenderEmail": "[email protected]",
    "Username": "[email protected]",
    "Password": "your-app-password"
  },
  "RateLimitSettings": {
    "MaxLoginAttempts": 5,
    "LockoutTimeMinutes": 15,
    "MaxRequestsPerMinute": 100
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
  

4. User Registration & Login System {#registration-login}

User Models & DTOs

Models/User.cs

  
    using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace JWTAuthDemo.Models
{
    public class User
    {
        [Key]
        public int Id { get; set; }
        
        [Required]
        [StringLength(50)]
        public string Username { get; set; } = string.Empty;
        
        [Required]
        [EmailAddress]
        public string Email { get; set; } = string.Empty;
        
        [Required]
        public string PasswordHash { get; set; } = string.Empty;
        
        [StringLength(100)]
        public string FirstName { get; set; } = string.Empty;
        
        [StringLength(100)]
        public string LastName { get; set; } = string.Empty;
        
        public DateTime DateOfBirth { get; set; }
        
        public string Role { get; set; } = "User";
        
        public bool IsActive { get; set; } = true;
        
        public bool EmailVerified { get; set; } = false;
        
        public string? EmailVerificationToken { get; set; }
        
        public DateTime? EmailVerificationTokenExpires { get; set; }
        
        public string? PasswordResetToken { get; set; }
        
        public DateTime? PasswordResetTokenExpires { get; set; }
        
        public int LoginAttempts { get; set; } = 0;
        
        public DateTime? LockoutEnd { get; set; }
        
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
        
        public DateTime? UpdatedAt { get; set; }
        
        public DateTime? LastLogin { get; set; }
        
        // Navigation properties
        public virtual ICollection<RefreshToken> RefreshTokens { get; set; } = new List<RefreshToken>();
        public virtual ICollection<LoginHistory> LoginHistory { get; set; } = new List<LoginHistory>();
        
        // Computed properties
        [NotMapped]
        public string FullName => $"{FirstName} {LastName}";
        
        [NotMapped]
        public bool IsLockedOut => LockoutEnd.HasValue && LockoutEnd > DateTime.UtcNow;
    }

    public class RefreshToken
    {
        [Key]
        public int Id { get; set; }
        
        [Required]
        public int UserId { get; set; }
        
        [Required]
        public string Token { get; set; } = string.Empty;
        
        public DateTime Expires { get; set; }
        
        public DateTime Created { get; set; } = DateTime.UtcNow;
        
        public string CreatedByIp { get; set; } = string.Empty;
        
        public DateTime? Revoked { get; set; }
        
        public string? RevokedByIp { get; set; }
        
        public string? ReplacedByToken { get; set; }
        
        public string? ReasonRevoked { get; set; }
        
        public bool IsExpired => DateTime.UtcNow >= Expires;
        public bool IsRevoked => Revoked != null;
        public bool IsActive => !IsRevoked && !IsExpired;
        
        // Navigation property
        public virtual User User { get; set; } = null!;
    }

    public class LoginHistory
    {
        [Key]
        public int Id { get; set; }
        
        [Required]
        public int UserId { get; set; }
        
        public DateTime LoginTime { get; set; } = DateTime.UtcNow;
        
        public string IpAddress { get; set; } = string.Empty;
        
        public string UserAgent { get; set; } = string.Empty;
        
        public bool Success { get; set; }
        
        public string? FailureReason { get; set; }
        
        // Navigation property
        public virtual User User { get; set; } = null!;
    }
}

// DTOs (Data Transfer Objects)
namespace JWTAuthDemo.Models
{
    public class RegisterRequest
    {
        [Required]
        [StringLength(50, MinimumLength = 3)]
        public string Username { get; set; } = string.Empty;

        [Required]
        [EmailAddress]
        public string Email { get; set; } = string.Empty;

        [Required]
        [StringLength(100, MinimumLength = 6)]
        public string Password { get; set; } = string.Empty;

        [Required]
        [Compare("Password")]
        public string ConfirmPassword { get; set; } = string.Empty;

        [Required]
        [StringLength(100)]
        public string FirstName { get; set; } = string.Empty;

        [Required]
        [StringLength(100)]
        public string LastName { get; set; } = string.Empty;

        [Required]
        public DateTime DateOfBirth { get; set; }
    }

    public class LoginRequest
    {
        [Required]
        public string Username { get; set; } = string.Empty;

        [Required]
        public string Password { get; set; } = string.Empty;
        
        public string? TwoFactorCode { get; set; }
    }

    public class AuthResponse
    {
        public bool Success { get; set; }
        public string Message { get; set; } = string.Empty;
        public string AccessToken { get; set; } = string.Empty;
        public string RefreshToken { get; set; } = string.Empty;
        public DateTime AccessTokenExpires { get; set; }
        public DateTime RefreshTokenExpires { get; set; }
        public UserResponse User { get; set; } = new();
    }

    public class UserResponse
    {
        public int Id { get; set; }
        public string Username { get; set; } = string.Empty;
        public string Email { get; set; } = string.Empty;
        public string FirstName { get; set; } = string.Empty;
        public string LastName { get; set; } = string.Empty;
        public string FullName { get; set; } = string.Empty;
        public string Role { get; set; } = string.Empty;
        public bool EmailVerified { get; set; }
        public DateTime? LastLogin { get; set; }
    }

    public class RefreshTokenRequest
    {
        [Required]
        public string AccessToken { get; set; } = string.Empty;
        
        [Required]
        public string RefreshToken { get; set; } = string.Empty;
    }

    public class ChangePasswordRequest
    {
        [Required]
        public string CurrentPassword { get; set; } = string.Empty;

        [Required]
        [StringLength(100, MinimumLength = 6)]
        public string NewPassword { get; set; } = string.Empty;

        [Required]
        [Compare("NewPassword")]
        public string ConfirmNewPassword { get; set; } = string.Empty;
    }

    public class ForgotPasswordRequest
    {
        [Required]
        [EmailAddress]
        public string Email { get; set; } = string.Empty;
    }

    public class ResetPasswordRequest
    {
        [Required]
        public string Token { get; set; } = string.Empty;

        [Required]
        [StringLength(100, MinimumLength = 6)]
        public string NewPassword { get; set; } = string.Empty;

        [Required]
        [Compare("NewPassword")]
        public string ConfirmNewPassword { get; set; } = string.Empty;
    }
}
  

Database Context

Data/ApplicationDbContext.cs

  
    using JWTAuthDemo.Models;
using Microsoft.EntityFrameworkCore;

namespace JWTAuthDemo.Data
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
        }

        public DbSet<User> Users { get; set; } = null!;
        public DbSet<RefreshToken> RefreshTokens { get; set; } = null!;
        public DbSet<LoginHistory> LoginHistory { get; set; } = null!;

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // User configuration
            modelBuilder.Entity<User>(entity =>
            {
                entity.HasIndex(u => u.Username).IsUnique();
                entity.HasIndex(u => u.Email).IsUnique();
                
                entity.Property(u => u.CreatedAt)
                    .HasDefaultValueSql("GETUTCDATE()");
                
                entity.HasMany(u => u.RefreshTokens)
                    .WithOne(rt => rt.User)
                    .HasForeignKey(rt => rt.UserId)
                    .OnDelete(DeleteBehavior.Cascade);
                    
                entity.HasMany(u => u.LoginHistory)
                    .WithOne(lh => lh.User)
                    .HasForeignKey(lh => lh.UserId)
                    .OnDelete(DeleteBehavior.Cascade);
            });

            // RefreshToken configuration
            modelBuilder.Entity<RefreshToken>(entity =>
            {
                entity.HasIndex(rt => rt.Token).IsUnique();
                entity.HasIndex(rt => rt.UserId);
                
                entity.Property(rt => rt.Created)
                    .HasDefaultValueSql("GETUTCDATE()");
            });

            // LoginHistory configuration
            modelBuilder.Entity<LoginHistory>(entity =>
            {
                entity.HasIndex(lh => lh.UserId);
                entity.HasIndex(lh => lh.LoginTime);
                
                entity.Property(lh => lh.LoginTime)
                    .HasDefaultValueSql("GETUTCDATE()");
            });

            // Seed initial data
            modelBuilder.Entity<User>().HasData(
                new User
                {
                    Id = 1,
                    Username = "admin",
                    Email = "[email protected]",
                    PasswordHash = BCrypt.Net.BCrypt.HashPassword("Admin123!"),
                    FirstName = "System",
                    LastName = "Administrator",
                    DateOfBirth = new DateTime(1980, 1, 1),
                    Role = "Admin",
                    EmailVerified = true,
                    IsActive = true
                }
            );
        }
    }
}
  

5. Token Generation & Validation {#token-generation}

Token Service Implementation

Services/ITokenService.cs

  
    using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using JWTAuthDemo.Models;

namespace JWTAuthDemo.Services
{
    public interface ITokenService
    {
        string GenerateAccessToken(User user);
        string GenerateRefreshToken();
        ClaimsPrincipal GetPrincipalFromExpiredToken(string token);
        bool ValidateToken(string token);
    }

    public class TokenService : ITokenService
    {
        private readonly IConfiguration _configuration;
        private readonly ILogger<TokenService> _logger;

        public TokenService(IConfiguration configuration, ILogger<TokenService> logger)
        {
            _configuration = configuration;
            _logger = logger;
        }

        public string GenerateAccessToken(User user)
        {
            try
            {
                var jwtSettings = _configuration.GetSection("JwtSettings");
                var secretKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(jwtSettings["SecretKey"]!));
                
                var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);

                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                    new Claim(ClaimTypes.Name, user.Username),
                    new Claim(ClaimTypes.Email, user.Email),
                    new Claim(ClaimTypes.GivenName, user.FirstName),
                    new Claim(ClaimTypes.Surname, user.LastName),
                    new Claim(ClaimTypes.Role, user.Role),
                    new Claim("DateOfBirth", user.DateOfBirth.ToString("yyyy-MM-dd")),
                    new Claim("EmailVerified", user.EmailVerified.ToString().ToLower()),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                    new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
                };

                var tokenOptions = new JwtSecurityToken(
                    issuer: jwtSettings["Issuer"],
                    audience: jwtSettings["Audience"],
                    claims: claims,
                    expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(jwtSettings["AccessTokenExpirationMinutes"])),
                    signingCredentials: signinCredentials
                );

                var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
                
                _logger.LogInformation("Access token generated for user: {Username}", user.Username);
                return tokenString;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error generating access token for user: {Username}", user.Username);
                throw;
            }
        }

        public string GenerateRefreshToken()
        {
            var randomNumber = new byte[64];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }

        public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
        {
            try
            {
                var jwtSettings = _configuration.GetSection("JwtSettings");
                var tokenValidationParameters = new TokenValidationParameters
                {
                    ValidateAudience = false, // We might don't have audience in refresh token
                    ValidateIssuer = false,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(jwtSettings["SecretKey"]!)),
                    ValidateLifetime = false // We want to get principal from expired token
                };

                var tokenHandler = new JwtSecurityTokenHandler();
                var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken securityToken);
                
                if (securityToken is not JwtSecurityToken jwtSecurityToken || 
                    !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new SecurityTokenException("Invalid token");
                }

                return principal;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting principal from expired token");
                throw;
            }
        }

        public bool ValidateToken(string token)
        {
            try
            {
                var jwtSettings = _configuration.GetSection("JwtSettings");
                var tokenHandler = new JwtSecurityTokenHandler();
                var validationParameters = GetValidationParameters();

                tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
                return true;
            }
            catch
            {
                return false;
            }
        }

        private TokenValidationParameters GetValidationParameters()
        {
            var jwtSettings = _configuration.GetSection("JwtSettings");
            
            return new TokenValidationParameters()
            {
                ValidateLifetime = true,
                ValidateAudience = true,
                ValidateIssuer = true,
                ValidIssuer = jwtSettings["Issuer"],
                ValidAudience = jwtSettings["Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(jwtSettings["SecretKey"]!))
            };
        }

        // Additional token utilities
        public DateTime GetTokenExpiration(string token)
        {
            var handler = new JwtSecurityTokenHandler();
            var jwtToken = handler.ReadJwtToken(token);
            return jwtToken.ValidTo;
        }

        public IEnumerable<Claim> GetTokenClaims(string token)
        {
            var handler = new JwtSecurityTokenHandler();
            var jwtToken = handler.ReadJwtToken(token);
            return jwtToken.Claims;
        }

        public bool IsTokenExpired(string token)
        {
            return GetTokenExpiration(token) < DateTime.UtcNow;
        }
    }
}
  

6. Claims & Role-Based Authorization {#claims-roles}

Advanced Authorization System

Services/IUserService.cs

  
    using JWTAuthDemo.Models;
using Microsoft.EntityFrameworkCore;

namespace JWTAuthDemo.Services
{
    public interface IUserService
    {
        Task<User?> AuthenticateAsync(string username, string password, string ipAddress);
        Task<User> RegisterAsync(RegisterRequest request, string ipAddress);
        Task<User?> GetUserByIdAsync(int id);
        Task<User?> GetUserByUsernameAsync(string username);
        Task<User?> GetUserByEmailAsync(string email);
        Task<bool> UserExistsAsync(string username, string email);
        Task<bool> UpdateUserAsync(User user);
        Task<bool> ChangePasswordAsync(int userId, ChangePasswordRequest request);
        Task<bool> VerifyEmailAsync(string token);
        Task<bool> ForgotPasswordAsync(string email);
        Task<bool> ResetPasswordAsync(ResetPasswordRequest request);
        Task LogLoginAttemptAsync(int userId, string ipAddress, string userAgent, bool success, string? failureReason = null);
        Task<bool> IsAccountLockedAsync(string username);
        Task ResetLoginAttemptsAsync(string username);
    }

    public class UserService : IUserService
    {
        private readonly ApplicationDbContext _context;
        private readonly ILogger<UserService> _logger;
        private readonly IEmailService _emailService;
        private readonly IConfiguration _configuration;

        public UserService(
            ApplicationDbContext context, 
            ILogger<UserService> logger,
            IEmailService emailService,
            IConfiguration configuration)
        {
            _context = context;
            _logger = logger;
            _emailService = emailService;
            _configuration = configuration;
        }

        public async Task<User?> AuthenticateAsync(string username, string password, string ipAddress)
        {
            try
            {
                var user = await _context.Users
                    .Include(u => u.RefreshTokens)
                    .FirstOrDefaultAsync(u => u.Username == username || u.Email == username);

                if (user == null)
                {
                    await LogLoginAttemptAsync(0, ipAddress, "Unknown", false, "User not found");
                    return null;
                }

                // Check if account is locked
                if (user.IsLockedOut)
                {
                    await LogLoginAttemptAsync(user.Id, ipAddress, "Unknown", false, "Account locked");
                    throw new ApplicationException("Account is temporarily locked due to multiple failed login attempts.");
                }

                // Check if account is active
                if (!user.IsActive)
                {
                    await LogLoginAttemptAsync(user.Id, ipAddress, "Unknown", false, "Account inactive");
                    throw new ApplicationException("Account is inactive.");
                }

                // Verify password
                if (!BCrypt.Net.BCrypt.Verify(password, user.PasswordHash))
                {
                    user.LoginAttempts++;
                    
                    // Lock account after maximum attempts
                    var maxAttempts = _configuration.GetValue<int>("RateLimitSettings:MaxLoginAttempts");
                    if (user.LoginAttempts >= maxAttempts)
                    {
                        var lockoutTime = _configuration.GetValue<int>("RateLimitSettings:LockoutTimeMinutes");
                        user.LockoutEnd = DateTime.UtcNow.AddMinutes(lockoutTime);
                        _logger.LogWarning("Account locked for user: {Username} after {Attempts} failed attempts", 
                            user.Username, user.LoginAttempts);
                    }

                    await _context.SaveChangesAsync();
                    await LogLoginAttemptAsync(user.Id, ipAddress, "Unknown", false, "Invalid password");
                    return null;
                }

                // Successful login
                user.LoginAttempts = 0;
                user.LockoutEnd = null;
                user.LastLogin = DateTime.UtcNow;
                await _context.SaveChangesAsync();

                await LogLoginAttemptAsync(user.Id, ipAddress, "Unknown", true);
                
                _logger.LogInformation("User authenticated successfully: {Username}", user.Username);
                return user;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error authenticating user: {Username}", username);
                throw;
            }
        }

        public async Task<User> RegisterAsync(RegisterRequest request, string ipAddress)
        {
            try
            {
                // Check if user already exists
                if (await UserExistsAsync(request.Username, request.Email))
                {
                    throw new ApplicationException("Username or email already exists.");
                }

                // Validate age (must be at least 13 years old)
                if (request.DateOfBirth > DateTime.Now.AddYears(-13))
                {
                    throw new ApplicationException("You must be at least 13 years old to register.");
                }

                var user = new User
                {
                    Username = request.Username,
                    Email = request.Email,
                    PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
                    FirstName = request.FirstName,
                    LastName = request.LastName,
                    DateOfBirth = request.DateOfBirth,
                    Role = "User",
                    IsActive = true,
                    EmailVerified = false,
                    EmailVerificationToken = GenerateRandomToken(),
                    EmailVerificationTokenExpires = DateTime.UtcNow.AddDays(1)
                };

                _context.Users.Add(user);
                await _context.SaveChangesAsync();

                // Send verification email
                await _emailService.SendVerificationEmailAsync(user.Email, user.EmailVerificationToken!);

                _logger.LogInformation("New user registered: {Username} from IP: {IpAddress}", 
                    user.Username, ipAddress);

                return user;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error registering user: {Username}", request.Username);
                throw;
            }
        }

        public async Task<User?> GetUserByIdAsync(int id)
        {
            return await _context.Users
                .Include(u => u.RefreshTokens)
                .FirstOrDefaultAsync(u => u.Id == id);
        }

        public async Task<User?> GetUserByUsernameAsync(string username)
        {
            return await _context.Users
                .FirstOrDefaultAsync(u => u.Username == username);
        }

        public async Task<User?> GetUserByEmailAsync(string email)
        {
            return await _context.Users
                .FirstOrDefaultAsync(u => u.Email == email);
        }

        public async Task<bool> UserExistsAsync(string username, string email)
        {
            return await _context.Users
                .AnyAsync(u => u.Username == username || u.Email == email);
        }

        public async Task<bool> UpdateUserAsync(User user)
        {
            try
            {
                user.UpdatedAt = DateTime.UtcNow;
                _context.Users.Update(user);
                await _context.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error updating user: {UserId}", user.Id);
                return false;
            }
        }

        public async Task<bool> ChangePasswordAsync(int userId, ChangePasswordRequest request)
        {
            try
            {
                var user = await GetUserByIdAsync(userId);
                if (user == null) return false;

                // Verify current password
                if (!BCrypt.Net.BCrypt.Verify(request.CurrentPassword, user.PasswordHash))
                {
                    throw new ApplicationException("Current password is incorrect.");
                }

                // Update password
                user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
                user.UpdatedAt = DateTime.UtcNow;

                await _context.SaveChangesAsync();
                
                _logger.LogInformation("Password changed for user: {UserId}", userId);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error changing password for user: {UserId}", userId);
                throw;
            }
        }

        public async Task<bool> VerifyEmailAsync(string token)
        {
            try
            {
                var user = await _context.Users
                    .FirstOrDefaultAsync(u => u.EmailVerificationToken == token && 
                                             u.EmailVerificationTokenExpires > DateTime.UtcNow);

                if (user == null) return false;

                user.EmailVerified = true;
                user.EmailVerificationToken = null;
                user.EmailVerificationTokenExpires = null;
                user.UpdatedAt = DateTime.UtcNow;

                await _context.SaveChangesAsync();
                
                _logger.LogInformation("Email verified for user: {Username}", user.Username);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error verifying email with token: {Token}", token);
                return false;
            }
        }

        public async Task<bool> ForgotPasswordAsync(string email)
        {
            try
            {
                var user = await GetUserByEmailAsync(email);
                if (user == null) return true; // Don't reveal if email exists

                user.PasswordResetToken = GenerateRandomToken();
                user.PasswordResetTokenExpires = DateTime.UtcNow.AddHours(1);

                await _context.SaveChangesAsync();

                // Send password reset email
                await _emailService.SendPasswordResetEmailAsync(user.Email, user.PasswordResetToken);

                _logger.LogInformation("Password reset token sent for user: {Email}", email);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing forgot password for: {Email}", email);
                return false;
            }
        }

        public async Task<bool> ResetPasswordAsync(ResetPasswordRequest request)
        {
            try
            {
                var user = await _context.Users
                    .FirstOrDefaultAsync(u => u.PasswordResetToken == request.Token && 
                                             u.PasswordResetTokenExpires > DateTime.UtcNow);

                if (user == null) return false;

                user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
                user.PasswordResetToken = null;
                user.PasswordResetTokenExpires = null;
                user.UpdatedAt = DateTime.UtcNow;

                await _context.SaveChangesAsync();
                
                _logger.LogInformation("Password reset successfully for user: {Username}", user.Username);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error resetting password with token: {Token}", request.Token);
                return false;
            }
        }

        public async Task LogLoginAttemptAsync(int userId, string ipAddress, string userAgent, bool success, string? failureReason = null)
        {
            try
            {
                var loginHistory = new LoginHistory
                {
                    UserId = userId,
                    IpAddress = ipAddress,
                    UserAgent = userAgent,
                    Success = success,
                    FailureReason = failureReason
                };

                _context.LoginHistory.Add(loginHistory);
                await _context.SaveChangesAsync();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error logging login attempt for user: {UserId}", userId);
            }
        }

        public async Task<bool> IsAccountLockedAsync(string username)
        {
            var user = await _context.Users
                .FirstOrDefaultAsync(u => u.Username == username || u.Email == username);
            
            return user?.IsLockedOut ?? false;
        }

        public async Task ResetLoginAttemptsAsync(string username)
        {
            var user = await _context.Users
                .FirstOrDefaultAsync(u => u.Username == username || u.Email == username);
            
            if (user != null)
            {
                user.LoginAttempts = 0;
                user.LockoutEnd = null;
                await _context.SaveChangesAsync();
            }
        }

        private string GenerateRandomToken()
        {
            var randomNumber = new byte[32];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber)
                .Replace("+", "-")
                .Replace("/", "_")
                .Replace("=", "");
        }
    }
}
  

7. Refresh Token Strategies {#refresh-tokens}

Refresh Token Service

Services/IRefreshTokenService.cs

  
    using JWTAuthDemo.Models;

namespace JWTAuthDemo.Services
{
    public interface IRefreshTokenService
    {
        Task<RefreshToken> GenerateRefreshTokenAsync(int userId, string ipAddress);
        Task<RefreshToken?> GetRefreshTokenAsync(string token);
        Task RevokeRefreshTokenAsync(string token, string ipAddress, string reason = "Revoked without replacement");
        Task RevokeAllRefreshTokensForUserAsync(int userId, string ipAddress, string reason = "Global revocation");
        Task<bool> IsRefreshTokenValidAsync(string token);
        Task CleanupExpiredRefreshTokensAsync();
    }

    public class RefreshTokenService : IRefreshTokenService
    {
        private readonly ApplicationDbContext _context;
        private readonly ILogger<RefreshTokenService> _logger;

        public RefreshTokenService(ApplicationDbContext context, ILogger<RefreshTokenService> logger)
        {
            _context = context;
            _logger = logger;
        }

        public async Task<RefreshToken> GenerateRefreshTokenAsync(int userId, string ipAddress)
        {
            try
            {
                var refreshToken = new RefreshToken
                {
                    UserId = userId,
                    Token = GenerateRandomToken(),
                    Expires = DateTime.UtcNow.AddDays(7), // 7 days expiration
                    CreatedByIp = ipAddress
                };

                _context.RefreshTokens.Add(refreshToken);
                await _context.SaveChangesAsync();

                _logger.LogInformation("Refresh token generated for user: {UserId}", userId);
                return refreshToken;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error generating refresh token for user: {UserId}", userId);
                throw;
            }
        }

        public async Task<RefreshToken?> GetRefreshTokenAsync(string token)
        {
            return await _context.RefreshTokens
                .Include(rt => rt.User)
                .FirstOrDefaultAsync(rt => rt.Token == token);
        }

        public async Task RevokeRefreshTokenAsync(string token, string ipAddress, string reason = "Revoked without replacement")
        {
            try
            {
                var refreshToken = await GetRefreshTokenAsync(token);
                if (refreshToken == null || refreshToken.IsRevoked) return;

                refreshToken.Revoked = DateTime.UtcNow;
                refreshToken.RevokedByIp = ipAddress;
                refreshToken.ReasonRevoked = reason;

                await _context.SaveChangesAsync();
                
                _logger.LogInformation("Refresh token revoked for user: {UserId}, reason: {Reason}", 
                    refreshToken.UserId, reason);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error revoking refresh token: {Token}", token);
                throw;
            }
        }

        public async Task RevokeAllRefreshTokensForUserAsync(int userId, string ipAddress, string reason = "Global revocation")
        {
            try
            {
                var activeTokens = await _context.RefreshTokens
                    .Where(rt => rt.UserId == userId && rt.IsActive)
                    .ToListAsync();

                foreach (var token in activeTokens)
                {
                    token.Revoked = DateTime.UtcNow;
                    token.RevokedByIp = ipAddress;
                    token.ReasonRevoked = reason;
                }

                await _context.SaveChangesAsync();
                
                _logger.LogInformation("All refresh tokens revoked for user: {UserId}, reason: {Reason}", 
                    userId, reason);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error revoking all refresh tokens for user: {UserId}", userId);
                throw;
            }
        }

        public async Task<bool> IsRefreshTokenValidAsync(string token)
        {
            var refreshToken = await GetRefreshTokenAsync(token);
            return refreshToken?.IsActive ?? false;
        }

        public async Task CleanupExpiredRefreshTokensAsync()
        {
            try
            {
                var expiredTokens = await _context.RefreshTokens
                    .Where(rt => rt.IsExpired || (rt.Revoked.HasValue && rt.Revoked < DateTime.UtcNow.AddDays(-30)))
                    .ToListAsync();

                _context.RefreshTokens.RemoveRange(expiredTokens);
                await _context.SaveChangesAsync();

                _logger.LogInformation("Cleaned up {Count} expired refresh tokens", expiredTokens.Count);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error cleaning up expired refresh tokens");
            }
        }

        private string GenerateRandomToken()
        {
            var randomNumber = new byte[64];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber)
                .Replace("+", "-")
                .Replace("/", "_")
                .Replace("=", "");
        }
    }
}
  

8. Password Security & Hashing {#password-security}

Advanced Password Security

Helpers/PasswordValidator.cs

  
    using System.Text.RegularExpressions;

namespace JWTAuthDemo.Helpers
{
    public static class PasswordValidator
    {
        public static PasswordValidationResult ValidatePassword(string password)
        {
            var result = new PasswordValidationResult { IsValid = true };

            if (string.IsNullOrWhiteSpace(password))
            {
                result.IsValid = false;
                result.Errors.Add("Password cannot be empty");
                return result;
            }

            // Minimum length
            if (password.Length < 8)
            {
                result.IsValid = false;
                result.Errors.Add("Password must be at least 8 characters long");
            }

            // Maximum length
            if (password.Length > 128)
            {
                result.IsValid = false;
                result.Errors.Add("Password cannot exceed 128 characters");
            }

            // Uppercase letters
            if (!Regex.IsMatch(password, "[A-Z]"))
            {
                result.IsValid = false;
                result.Errors.Add("Password must contain at least one uppercase letter");
            }

            // Lowercase letters
            if (!Regex.IsMatch(password, "[a-z]"))
            {
                result.IsValid = false;
                result.Errors.Add("Password must contain at least one lowercase letter");
            }

            // Numbers
            if (!Regex.IsMatch(password, "[0-9]"))
            {
                result.IsValid = false;
                result.Errors.Add("Password must contain at least one number");
            }

            // Special characters
            if (!Regex.IsMatch(password, "[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]"))
            {
                result.IsValid = false;
                result.Errors.Add("Password must contain at least one special character");
            }

            // Common passwords check
            if (IsCommonPassword(password))
            {
                result.IsValid = false;
                result.Errors.Add("Password is too common. Please choose a more unique password");
            }

            // Sequential characters check
            if (HasSequentialCharacters(password, 3))
            {
                result.IsValid = false;
                result.Errors.Add("Password contains sequential characters");
            }

            // Repeated characters check
            if (HasRepeatedCharacters(password, 3))
            {
                result.IsValid = false;
                result.Errors.Add("Password contains repeated characters");
            }

            return result;
        }

        public static int CalculatePasswordStrength(string password)
        {
            if (string.IsNullOrEmpty(password)) return 0;

            int score = 0;

            // Length score
            if (password.Length >= 8) score++;
            if (password.Length >= 12) score++;
            if (password.Length >= 16) score++;

            // Character variety score
            if (Regex.IsMatch(password, "[a-z]")) score++;
            if (Regex.IsMatch(password, "[A-Z]")) score++;
            if (Regex.IsMatch(password, "[0-9]")) score++;
            if (Regex.IsMatch(password, "[^a-zA-Z0-9]")) score++;

            // Bonus points for complexity
            if (password.Length >= 20) score++;
            if (Regex.IsMatch(password, "(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9])")) score++;

            return Math.Min(score, 10); // Max score 10
        }

        private static bool IsCommonPassword(string password)
        {
            var commonPasswords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
            {
                "password", "123456", "12345678", "1234", "qwerty", "12345",
                "dragon", "baseball", "football", "letmein", "monkey",
                "abc123", "password1", "admin", "welcome", "login"
            };

            return commonPasswords.Contains(password);
        }

        private static bool HasSequentialCharacters(string str, int sequenceLength)
        {
            for (int i = 0; i <= str.Length - sequenceLength; i++)
            {
                var segment = str.Substring(i, sequenceLength);
                if (IsSequential(segment))
                    return true;
            }
            return false;
        }

        private static bool HasRepeatedCharacters(string str, int repeatCount)
        {
            for (int i = 0; i <= str.Length - repeatCount; i++)
            {
                var segment = str.Substring(i, repeatCount);
                if (segment.Distinct().Count() == 1)
                    return true;
            }
            return false;
        }

        private static bool IsSequential(string str)
        {
            // Check for sequential numbers or letters
            var isNumeric = str.All(char.IsDigit);
            var isAlpha = str.All(char.IsLetter);

            if (isNumeric)
            {
                for (int i = 1; i < str.Length; i++)
                {
                    if (str[i] - str[i - 1] != 1)
                        return false;
                }
                return true;
            }

            if (isAlpha)
            {
                for (int i = 1; i < str.Length; i++)
                {
                    if (char.ToLower(str[i]) - char.ToLower(str[i - 1]) != 1)
                        return false;
                }
                return true;
            }

            return false;
        }
    }

    public class PasswordValidationResult
    {
        public bool IsValid { get; set; }
        public List<string> Errors { get; set; } = new List<string>();
        public int StrengthScore { get; set; }

        public string GetStrengthDescription()
        {
            return StrengthScore switch
            {
                >= 8 => "Very Strong",
                >= 6 => "Strong",
                >= 4 => "Medium",
                >= 2 => "Weak",
                _ => "Very Weak"
            };
        }
    }
}
  

9. Advanced Security Middleware {#security-middleware}

Security Headers & Rate Limiting

Middleware/SecurityHeadersMiddleware.cs

  
    using Microsoft.AspNetCore.Http.Features;

namespace JWTAuthDemo.Middleware
{
    public class SecurityHeadersMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<SecurityHeadersMiddleware> _logger;

        public SecurityHeadersMiddleware(RequestDelegate next, ILogger<SecurityHeadersMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            // Add security headers
            context.Response.Headers.Add("X-Frame-Options", "DENY");
            context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
            context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
            context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
            context.Response.Headers.Add("Permissions-Policy", "geolocation=(), microphone=()");
            
            // Content Security Policy
            context.Response.Headers.Add("Content-Security-Policy", 
                "default-src 'self'; " +
                "script-src 'self' 'unsafe-inline'; " +
                "style-src 'self' 'unsafe-inline'; " +
                "img-src 'self' data:; " +
                "connect-src 'self'; " +
                "font-src 'self'; " +
                "object-src 'none'; " +
                "media-src 'self'; " +
                "frame-src 'none'; " +
                "base-uri 'self';");

            // Remove server header
            context.Response.Headers.Remove("Server");

            await _next(context);
        }
    }

    public class RateLimitingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<RateLimitingMiddleware> _logger;
        private static readonly Dictionary<string, List<DateTime>> _requestLog = new();

        public RateLimitingMiddleware(RequestDelegate next, ILogger<RateLimitingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var clientIp = GetClientIpAddress(context);
            var now = DateTime.UtcNow;

            if (!_requestLog.ContainsKey(clientIp))
            {
                _requestLog[clientIp] = new List<DateTime>();
            }

            var clientRequests = _requestLog[clientIp];

            // Clean old requests (older than 1 minute)
            clientRequests.RemoveAll(t => t < now.AddMinutes(-1));

            // Check rate limit (100 requests per minute)
            if (clientRequests.Count >= 100)
            {
                _logger.LogWarning("Rate limit exceeded for IP: {ClientIp}", clientIp);
                context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
                await context.Response.WriteAsync("Rate limit exceeded. Please try again later.");
                return;
            }

            clientRequests.Add(now);

            await _next(context);
        }

        private string GetClientIpAddress(HttpContext context)
        {
            // Check for forwarded header (behind proxy)
            if (context.Request.Headers.ContainsKey("X-Forwarded-For"))
                return context.Request.Headers["X-Forwarded-For"].ToString();

            return context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        }
    }

    // Extension methods for easy middleware registration
    public static class SecurityMiddlewareExtensions
    {
        public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app)
        {
            return app.UseMiddleware<SecurityHeadersMiddleware>();
        }

        public static IApplicationBuilder UseRateLimitingMiddleware(this IApplicationBuilder app)
        {
            return app.UseMiddleware<RateLimitingMiddleware>();
        }
    }
}
  

10. Two-Factor Authentication (2FA) {#2fa}

2FA Implementation

Services/TwoFactorService.cs

  
    using System.Security.Cryptography;
using Microsoft.AspNetCore.Identity;

namespace JWTAuthDemo.Services
{
    public interface ITwoFactorService
    {
        string GenerateTwoFactorCode();
        bool VerifyTwoFactorCode(string code, string storedCode);
        Task<bool> SendTwoFactorCodeAsync(string email, string code);
        string GenerateRecoveryCodes(int count = 5);
    }

    public class TwoFactorService : ITwoFactorService
    {
        private readonly IEmailService _emailService;
        private readonly ILogger<TwoFactorService> _logger;

        public TwoFactorService(IEmailService emailService, ILogger<TwoFactorService> logger)
        {
            _emailService = emailService;
            _logger = logger;
        }

        public string GenerateTwoFactorCode()
        {
            // Generate a 6-digit code
            var random = new Random();
            return random.Next(100000, 999999).ToString();
        }

        public bool VerifyTwoFactorCode(string code, string storedCode)
        {
            if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(storedCode))
                return false;

            // Simple comparison - in production, you might want to hash the codes
            return code == storedCode;
        }

        public async Task<bool> SendTwoFactorCodeAsync(string email, string code)
        {
            try
            {
                var subject = "Your Two-Factor Authentication Code";
                var body = $@"
                    <h2>Two-Factor Authentication</h2>
                    <p>Your verification code is: <strong>{code}</strong></p>
                    <p>This code will expire in 10 minutes.</p>
                    <p>If you didn't request this code, please ignore this email.</p>";

                await _emailService.SendEmailAsync(email, subject, body);
                
                _logger.LogInformation("2FA code sent to: {Email}", email);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error sending 2FA code to: {Email}", email);
                return false;
            }
        }

        public string GenerateRecoveryCodes(int count = 5)
        {
            var codes = new List<string>();
            for (int i = 0; i < count; i++)
            {
                codes.Add(GenerateRecoveryCode());
            }
            return string.Join(",", codes);
        }

        private string GenerateRecoveryCode()
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            var random = new Random();
            return new string(Enumerable.Repeat(chars, 8)
                .Select(s => s[random.Next(s.Length)]).ToArray());
        }
    }
}
  

11. Authentication Controllers {#controllers}

Complete Auth Controller

Controllers/AuthController.cs

  
    using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using JWTAuthDemo.Models;
using JWTAuthDemo.Services;
using JWTAuthDemo.Helpers;

namespace JWTAuthDemo.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AuthController : ControllerBase
    {
        private readonly IUserService _userService;
        private readonly ITokenService _tokenService;
        private readonly IRefreshTokenService _refreshTokenService;
        private readonly ITwoFactorService _twoFactorService;
        private readonly ILogger<AuthController> _logger;

        public AuthController(
            IUserService userService,
            ITokenService tokenService,
            IRefreshTokenService refreshTokenService,
            ITwoFactorService twoFactorService,
            ILogger<AuthController> logger)
        {
            _userService = userService;
            _tokenService = tokenService;
            _refreshTokenService = refreshTokenService;
            _twoFactorService = twoFactorService;
            _logger = logger;
        }

        [HttpPost("register")]
        public async Task<ActionResult<AuthResponse>> Register(RegisterRequest request)
        {
            try
            {
                // Validate password strength
                var passwordValidation = PasswordValidator.ValidatePassword(request.Password);
                if (!passwordValidation.IsValid)
                {
                    return BadRequest(new AuthResponse
                    {
                        Success = false,
                        Message = string.Join(", ", passwordValidation.Errors)
                    });
                }

                var ipAddress = GetIpAddress();
                var user = await _userService.RegisterAsync(request, ipAddress);

                // Generate tokens
                var accessToken = _tokenService.GenerateAccessToken(user);
                var refreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(user.Id, ipAddress);

                var response = new AuthResponse
                {
                    Success = true,
                    Message = "Registration successful. Please check your email for verification.",
                    AccessToken = accessToken,
                    RefreshToken = refreshToken.Token,
                    AccessTokenExpires = _tokenService.GetTokenExpiration(accessToken),
                    RefreshTokenExpires = refreshToken.Expires,
                    User = new UserResponse
                    {
                        Id = user.Id,
                        Username = user.Username,
                        Email = user.Email,
                        FirstName = user.FirstName,
                        LastName = user.LastName,
                        FullName = user.FullName,
                        Role = user.Role,
                        EmailVerified = user.EmailVerified,
                        LastLogin = user.LastLogin
                    }
                };

                _logger.LogInformation("User registered successfully: {Username}", user.Username);
                return Ok(response);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error during registration for user: {Username}", request.Username);
                return BadRequest(new AuthResponse
                {
                    Success = false,
                    Message = ex.Message
                });
            }
        }

        [HttpPost("login")]
        public async Task<ActionResult<AuthResponse>> Login(LoginRequest request)
        {
            try
            {
                var ipAddress = GetIpAddress();

                // Check if account is locked
                if (await _userService.IsAccountLockedAsync(request.Username))
                {
                    return Unauthorized(new AuthResponse
                    {
                        Success = false,
                        Message = "Account is temporarily locked due to multiple failed login attempts."
                    });
                }

                var user = await _userService.AuthenticateAsync(request.Username, request.Password, ipAddress);
                if (user == null)
                {
                    return Unauthorized(new AuthResponse
                    {
                        Success = false,
                        Message = "Invalid username or password"
                    });
                }

                // Check if email is verified
                if (!user.EmailVerified)
                {
                    return Unauthorized(new AuthResponse
                    {
                        Success = false,
                        Message = "Please verify your email before logging in."
                    });
                }

                // Generate tokens
                var accessToken = _tokenService.GenerateAccessToken(user);
                var refreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(user.Id, ipAddress);

                var response = new AuthResponse
                {
                    Success = true,
                    Message = "Login successful",
                    AccessToken = accessToken,
                    RefreshToken = refreshToken.Token,
                    AccessTokenExpires = _tokenService.GetTokenExpiration(accessToken),
                    RefreshTokenExpires = refreshToken.Expires,
                    User = new UserResponse
                    {
                        Id = user.Id,
                        Username = user.Username,
                        Email = user.Email,
                        FirstName = user.FirstName,
                        LastName = user.LastName,
                        FullName = user.FullName,
                        Role = user.Role,
                        EmailVerified = user.EmailVerified,
                        LastLogin = user.LastLogin
                    }
                };

                _logger.LogInformation("User logged in successfully: {Username}", user.Username);
                return Ok(response);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error during login for user: {Username}", request.Username);
                return Unauthorized(new AuthResponse
                {
                    Success = false,
                    Message = ex.Message
                });
            }
        }

        [HttpPost("refresh-token")]
        public async Task<ActionResult<AuthResponse>> RefreshToken(RefreshTokenRequest request)
        {
            try
            {
                var ipAddress = GetIpAddress();

                // Validate the expired access token
                var principal = _tokenService.GetPrincipalFromExpiredToken(request.AccessToken);
                var userId = int.Parse(principal.FindFirst(ClaimTypes.NameIdentifier)!.Value);

                // Validate refresh token
                var refreshToken = await _refreshTokenService.GetRefreshTokenAsync(request.RefreshToken);
                if (refreshToken == null || !refreshToken.IsActive || refreshToken.UserId != userId)
                {
                    return Unauthorized(new AuthResponse
                    {
                        Success = false,
                        Message = "Invalid refresh token"
                    });
                }

                // Revoke the used refresh token
                await _refreshTokenService.RevokeRefreshTokenAsync(
                    request.RefreshToken, ipAddress, "Replaced by new token");

                var user = await _userService.GetUserByIdAsync(userId);
                if (user == null)
                {
                    return Unauthorized(new AuthResponse
                    {
                        Success = false,
                        Message = "User not found"
                    });
                }

                // Generate new tokens
                var newAccessToken = _tokenService.GenerateAccessToken(user);
                var newRefreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(user.Id, ipAddress);

                var response = new AuthResponse
                {
                    Success = true,
                    Message = "Token refreshed successfully",
                    AccessToken = newAccessToken,
                    RefreshToken = newRefreshToken.Token,
                    AccessTokenExpires = _tokenService.GetTokenExpiration(newAccessToken),
                    RefreshTokenExpires = newRefreshToken.Expires,
                    User = new UserResponse
                    {
                        Id = user.Id,
                        Username = user.Username,
                        Email = user.Email,
                        FirstName = user.FirstName,
                        LastName = user.LastName,
                        FullName = user.FullName,
                        Role = user.Role,
                        EmailVerified = user.EmailVerified,
                        LastLogin = user.LastLogin
                    }
                };

                _logger.LogInformation("Token refreshed for user: {UserId}", userId);
                return Ok(response);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error refreshing token");
                return Unauthorized(new AuthResponse
                {
                    Success = false,
                    Message = "Invalid token"
                });
            }
        }

        [HttpPost("revoke-token")]
        [Authorize]
        public async Task<IActionResult> RevokeToken(RefreshTokenRequest request)
        {
            try
            {
                var ipAddress = GetIpAddress();
                await _refreshTokenService.RevokeRefreshTokenAsync(request.RefreshToken, ipAddress, "User requested revocation");

                _logger.LogInformation("Token revoked by user");
                return Ok(new { Success = true, Message = "Token revoked successfully" });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error revoking token");
                return BadRequest(new { Success = false, Message = "Error revoking token" });
            }
        }

        [HttpPost("change-password")]
        [Authorize]
        public async Task<IActionResult> ChangePassword(ChangePasswordRequest request)
        {
            try
            {
                var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value);
                var result = await _userService.ChangePasswordAsync(userId, request);

                if (result)
                {
                    // Revoke all refresh tokens for security
                    var ipAddress = GetIpAddress();
                    await _refreshTokenService.RevokeAllRefreshTokensForUserAsync(userId, ipAddress, "Password changed");

                    _logger.LogInformation("Password changed for user: {UserId}", userId);
                    return Ok(new { Success = true, Message = "Password changed successfully" });
                }

                return BadRequest(new { Success = false, Message = "Error changing password" });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error changing password");
                return BadRequest(new { Success = false, Message = ex.Message });
            }
        }

        [HttpPost("verify-email")]
        public async Task<IActionResult> VerifyEmail([FromQuery] string token)
        {
            try
            {
                var result = await _userService.VerifyEmailAsync(token);
                if (result)
                {
                    return Ok(new { Success = true, Message = "Email verified successfully" });
                }

                return BadRequest(new { Success = false, Message = "Invalid or expired verification token" });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error verifying email with token: {Token}", token);
                return BadRequest(new { Success = false, Message = "Error verifying email" });
            }
        }

        [HttpPost("forgot-password")]
        public async Task<IActionResult> ForgotPassword(ForgotPasswordRequest request)
        {
            try
            {
                var result = await _userService.ForgotPasswordAsync(request.Email);
                return Ok(new { 
                    Success = true, 
                    Message = "If the email exists, a password reset link has been sent." 
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing forgot password for: {Email}", request.Email);
                return BadRequest(new { Success = false, Message = "Error processing request" });
            }
        }

        [HttpPost("reset-password")]
        public async Task<IActionResult> ResetPassword(ResetPasswordRequest request)
        {
            try
            {
                var result = await _userService.ResetPasswordAsync(request);
                if (result)
                {
                    return Ok(new { Success = true, Message = "Password reset successfully" });
                }

                return BadRequest(new { Success = false, Message = "Invalid or expired reset token" });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error resetting password");
                return BadRequest(new { Success = false, Message = "Error resetting password" });
            }
        }

        [HttpGet("profile")]
        [Authorize]
        public async Task<ActionResult<UserResponse>> GetProfile()
        {
            try
            {
                var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value);
                var user = await _userService.GetUserByIdAsync(userId);

                if (user == null)
                {
                    return NotFound(new { Success = false, Message = "User not found" });
                }

                var response = new UserResponse
                {
                    Id = user.Id,
                    Username = user.Username,
                    Email = user.Email,
                    FirstName = user.FirstName,
                    LastName = user.LastName,
                    FullName = user.FullName,
                    Role = user.Role,
                    EmailVerified = user.EmailVerified,
                    LastLogin = user.LastLogin
                };

                return Ok(response);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting user profile");
                return BadRequest(new { Success = false, Message = "Error getting profile" });
            }
        }

        [HttpPost("logout")]
        [Authorize]
        public async Task<IActionResult> Logout()
        {
            try
            {
                var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value);
                var ipAddress = GetIpAddress();

                // Revoke all refresh tokens for the user
                await _refreshTokenService.RevokeAllRefreshTokensForUserAsync(userId, ipAddress, "User logged out");

                _logger.LogInformation("User logged out: {UserId}", userId);
                return Ok(new { Success = true, Message = "Logged out successfully" });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error during logout");
                return BadRequest(new { Success = false, Message = "Error during logout" });
            }
        }

        private string GetIpAddress()
        {
            // Check for forwarded header (behind proxy)
            if (Request.Headers.ContainsKey("X-Forwarded-For"))
                return Request.Headers["X-Forwarded-For"].ToString();

            return HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        }
    }
}
  

This comprehensive JWT authentication guide provides complete, production-ready examples that cover all aspects of modern security implementation in  ASP.NET  Core. The code includes advanced features like refresh tokens, rate limiting, password policies, and comprehensive error handling.

This JWT authentication implementation provides enterprise-grade security features that you can immediately implement in your  ASP.NET  Core applications. The guide covers everything from basic token generation to advanced security patterns and production-ready deployment strategies.