Files
pos-system/microservices/.agent/skills/security/references/REFERENCE.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

17 KiB

Security Patterns - Detailed Reference

Detailed code examples for security patterns in ASP.NET Core.

Table of Contents

  1. Authentication with Identity
  2. JWT Configuration
  3. Authorization Policies
  4. Password Hashing
  5. Two-Factor Authentication
  6. Input Validation
  7. Rate Limiting
  8. Global Exception Handler
  9. Audit Logging
  10. CORS Configuration

Authentication with Identity

Register User Command Handler

/// <summary>
/// EN: Handler for user registration.
/// VI: Handler cho việc đăng ký user.
/// </summary>
public class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand, RegisterResult>
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly ILogger<RegisterUserCommandHandler> _logger;

    public RegisterUserCommandHandler(
        UserManager<ApplicationUser> userManager,
        ILogger<RegisterUserCommandHandler> logger)
    {
        _userManager = userManager;
        _logger = logger;
    }

    public async Task<RegisterResult> Handle(
        RegisterUserCommand request,
        CancellationToken cancellationToken)
    {
        // EN: Check if user exists / VI: Kiểm tra user đã tồn tại
        var existingUser = await _userManager.FindByEmailAsync(request.Email);
        if (existingUser != null)
        {
            throw new ConflictException("User with this email already exists");
        }

        // EN: Create new user / VI: Tạo user mới
        var user = new ApplicationUser
        {
            UserName = request.Email,
            Email = request.Email,
            FullName = request.FullName,
            EmailConfirmed = false
        };

        // EN: Identity handles password hashing automatically
        // VI: Identity tự động hash password
        var result = await _userManager.CreateAsync(user, request.Password);

        if (!result.Succeeded)
        {
            var errors = string.Join(", ", result.Errors.Select(e => e.Description));
            throw new ValidationException(errors);
        }

        // EN: Assign default role / VI: Gán role mặc định
        await _userManager.AddToRoleAsync(user, "User");

        _logger.LogInformation(
            "EN: User registered successfully / VI: Đăng ký user thành công: {Email}", 
            user.Email);

        return new RegisterResult(user.Id, user.Email!);
    }
}

JWT Configuration

Program.cs Configuration

// EN: Configure JWT authentication / VI: Cấu hình JWT authentication
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.Authority = builder.Configuration["IdentityServer:Authority"];
    options.Audience = builder.Configuration["IdentityServer:Audience"];
    options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();
    
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ClockSkew = TimeSpan.FromMinutes(5)
    };

    options.Events = new JwtBearerEvents
    {
        OnAuthenticationFailed = context =>
        {
            var logger = context.HttpContext.RequestServices
                .GetRequiredService<ILogger<Program>>();
            logger.LogWarning(
                "EN: JWT authentication failed / VI: JWT authentication thất bại: {Error}",
                context.Exception.Message);
            return Task.CompletedTask;
        }
    };
});

Authorization Policies

Configure Policies

// EN: Configure authorization policies / VI: Cấu hình authorization policies
builder.Services.AddAuthorization(options =>
{
    // EN: Role-based policy / VI: Policy dựa trên role
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"));

    // EN: Multiple roles / VI: Nhiều roles
    options.AddPolicy("CanManageUsers", policy =>
        policy.RequireRole("Admin", "Manager"));

    // EN: Claim-based policy / VI: Policy dựa trên claim
    options.AddPolicy("PremiumUser", policy =>
        policy.RequireClaim("subscription", "premium"));

    // EN: Custom requirement / VI: Requirement tùy chỉnh
    options.AddPolicy("ResourceOwner", policy =>
        policy.AddRequirements(new ResourceOwnerRequirement()));
});

/// <summary>
/// EN: Custom authorization requirement.
/// VI: Authorization requirement tùy chỉnh.
/// </summary>
public class ResourceOwnerRequirement : IAuthorizationRequirement { }

public class ResourceOwnerHandler : AuthorizationHandler<ResourceOwnerRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        ResourceOwnerRequirement requirement)
    {
        var userId = context.User.FindFirst("sub")?.Value;
        var resourceOwnerId = context.Resource as string;

        if (userId == resourceOwnerId)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Password Hashing

ASP.NET Core Identity (Automatic)

/// <summary>
/// EN: Identity uses PBKDF2 with HMAC-SHA256 by default.
/// VI: Identity sử dụng PBKDF2 với HMAC-SHA256 mặc định.
/// </summary>

// EN: Creating user with password (auto-hashed)
// VI: Tạo user với password (tự động hash)
await _userManager.CreateAsync(user, password);

// EN: Verify password / VI: Xác minh password
var isValid = await _userManager.CheckPasswordAsync(user, password);

// EN: Change password / VI: Đổi password
await _userManager.ChangePasswordAsync(user, currentPassword, newPassword);

// EN: Reset password with token / VI: Reset password với token
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
await _userManager.ResetPasswordAsync(user, token, newPassword);

Custom Password Hasher (If Needed)

/// <summary>
/// EN: Configure password hasher options.
/// VI: Cấu hình password hasher options.
/// </summary>
builder.Services.Configure<PasswordHasherOptions>(options =>
{
    // EN: Iteration count (higher = more secure but slower)
    // VI: Số lần lặp (cao hơn = bảo mật hơn nhưng chậm hơn)
    options.IterationCount = 100000;
});

Two-Factor Authentication

Enable 2FA Command Handler

/// <summary>
/// EN: Handler for enabling 2FA.
/// VI: Handler cho việc bật 2FA.
/// </summary>
public class Enable2FACommandHandler : IRequestHandler<Enable2FACommand, Enable2FAResult>
{
    private readonly UserManager<ApplicationUser> _userManager;
    private const string AuthenticatorUriFormat = 
        "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";

    public async Task<Enable2FAResult> Handle(
        Enable2FACommand request,
        CancellationToken ct)
    {
        var user = await _userManager.FindByIdAsync(request.UserId.ToString());
        if (user == null)
            throw new NotFoundException("User not found");

        // EN: Check if 2FA already enabled / VI: Kiểm tra 2FA đã bật chưa
        if (await _userManager.GetTwoFactorEnabledAsync(user))
            throw new InvalidOperationException("2FA is already enabled");

        // EN: Generate authenticator key / VI: Tạo key authenticator
        await _userManager.ResetAuthenticatorKeyAsync(user);
        var key = await _userManager.GetAuthenticatorKeyAsync(user);

        // EN: Generate QR code / VI: Tạo QR code
        var uri = string.Format(
            AuthenticatorUriFormat,
            "GoodGo",
            user.Email,
            key);
        var qrCode = GenerateQrCode(uri);

        // EN: Generate recovery codes / VI: Tạo mã khôi phục
        var recoveryCodes = await _userManager
            .GenerateNewTwoFactorRecoveryCodesAsync(user, 10);

        return new Enable2FAResult
        {
            QrCodeBase64 = qrCode,
            ManualEntryKey = key!,
            RecoveryCodes = recoveryCodes?.ToArray() ?? Array.Empty<string>()
        };
    }

    private string GenerateQrCode(string uri)
    {
        using var qrGenerator = new QRCodeGenerator();
        using var qrData = qrGenerator.CreateQrCode(uri, QRCodeGenerator.ECCLevel.Q);
        using var qrCode = new PngByteQRCode(qrData);
        var qrBytes = qrCode.GetGraphic(20);
        return Convert.ToBase64String(qrBytes);
    }
}

Verify 2FA Code

/// <summary>
/// EN: Verify TOTP code during login.
/// VI: Xác minh mã TOTP khi đăng nhập.
/// </summary>
public async Task<bool> VerifyTwoFactorCodeAsync(
    ApplicationUser user, 
    string code)
{
    // EN: Verify TOTP code / VI: Xác minh mã TOTP
    var isValid = await _userManager
        .VerifyTwoFactorTokenAsync(
            user,
            _userManager.Options.Tokens.AuthenticatorTokenProvider,
            code);

    if (!isValid)
    {
        // EN: Try recovery code / VI: Thử mã khôi phục
        var result = await _userManager
            .RedeemTwoFactorRecoveryCodeAsync(user, code);
        return result.Succeeded;
    }

    return true;
}

Input Validation

FluentValidation Setup

// EN: Register FluentValidation / VI: Đăng ký FluentValidation
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
builder.Services.AddFluentValidationAutoValidation();

/// <summary>
/// EN: Validator for register command.
/// VI: Validator cho register command.
/// </summary>
public class RegisterUserCommandValidator : AbstractValidator<RegisterUserCommand>
{
    public RegisterUserCommandValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("Email is required")
            .EmailAddress().WithMessage("Invalid email format");

        RuleFor(x => x.Password)
            .NotEmpty().WithMessage("Password is required")
            .MinimumLength(8).WithMessage("Password must be at least 8 characters")
            .Matches("[A-Z]").WithMessage("Password must contain uppercase letter")
            .Matches("[a-z]").WithMessage("Password must contain lowercase letter")
            .Matches("[0-9]").WithMessage("Password must contain digit")
            .Matches("[^a-zA-Z0-9]").WithMessage("Password must contain special character");

        RuleFor(x => x.FullName)
            .MaximumLength(100).WithMessage("Name cannot exceed 100 characters");
    }
}

Rate Limiting

ASP.NET Core Rate Limiting

// EN: Configure rate limiting / VI: Cấu hình rate limiting
builder.Services.AddRateLimiter(options =>
{
    // EN: Standard rate limit / VI: Rate limit chuẩn
    options.AddFixedWindowLimiter("standard", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(15);
        opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        opt.QueueLimit = 5;
    });

    // EN: Strict rate limit for sensitive operations
    // VI: Rate limit nghiêm ngặt cho operations nhạy cảm
    options.AddFixedWindowLimiter("strict", opt =>
    {
        opt.PermitLimit = 10;
        opt.Window = TimeSpan.FromHours(1);
    });

    // EN: Login rate limit / VI: Rate limit cho login
    options.AddSlidingWindowLimiter("login", opt =>
    {
        opt.PermitLimit = 5;
        opt.Window = TimeSpan.FromMinutes(15);
        opt.SegmentsPerWindow = 3;
    });

    options.OnRejected = async (context, token) =>
    {
        context.HttpContext.Response.StatusCode = 429;
        await context.HttpContext.Response.WriteAsJsonAsync(
            ApiResponse<object>.Fail("RATE_LIMITED", "Too many requests"),
            token);
    };
});

// EN: Apply rate limiting / VI: Áp dụng rate limiting  
app.UseRateLimiter();

// EN: Use on specific endpoints / VI: Dùng cho endpoints cụ thể
[EnableRateLimiting("login")]
[HttpPost("login")]
public async Task<IActionResult> Login(...) { }

Global Exception Handler

Exception Handler Middleware

/// <summary>
/// EN: Global exception handler middleware.
/// VI: Middleware xử lý exception toàn cục.
/// </summary>
public class ExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlerMiddleware> _logger;

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

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception ex)
    {
        // EN: Log full error details / VI: Log chi tiết lỗi đầy đủ
        _logger.LogError(ex, 
            "EN: Unhandled exception / VI: Exception không xử lý: {Path}", 
            context.Request.Path);

        var (statusCode, message) = ex switch
        {
            ValidationException ve => (400, ve.Message),
            UnauthorizedAccessException => (401, "Unauthorized"),
            NotFoundException => (404, "Resource not found"),
            ConflictException => (409, "Resource already exists"),
            _ => (500, "An unexpected error occurred")
        };

        context.Response.StatusCode = statusCode;
        context.Response.ContentType = "application/json";

        // EN: Don't expose internal errors / VI: Không lộ lỗi internal
        var response = ApiResponse<object>.Fail(
            statusCode == 500 ? "INTERNAL_ERROR" : "ERROR",
            message);

        await context.Response.WriteAsJsonAsync(response);
    }
}

// EN: Register in Program.cs / VI: Đăng ký trong Program.cs
app.UseMiddleware<ExceptionHandlerMiddleware>();

Audit Logging

Audit Service

/// <summary>
/// EN: Service for audit logging.
/// VI: Service cho audit logging.
/// </summary>
public interface IAuditService
{
    Task LogSecurityEventAsync(
        string eventType,
        Guid? userId,
        object? details,
        HttpContext? context = null);
}

public class AuditService : IAuditService
{
    private readonly ApplicationDbContext _db;
    private readonly ILogger<AuditService> _logger;

    public async Task LogSecurityEventAsync(
        string eventType,
        Guid? userId,
        object? details,
        HttpContext? context = null)
    {
        var sanitizedDetails = SanitizeDetails(details);

        var auditLog = new AuditLog
        {
            EventType = eventType,
            UserId = userId,
            Details = JsonSerializer.Serialize(sanitizedDetails),
            IpAddress = context?.Connection.RemoteIpAddress?.ToString(),
            UserAgent = context?.Request.Headers.UserAgent.FirstOrDefault(),
            Timestamp = DateTime.UtcNow
        };

        _db.AuditLogs.Add(auditLog);
        await _db.SaveChangesAsync();

        _logger.LogInformation(
            "EN: Security event / VI: Sự kiện bảo mật: {EventType} UserId: {UserId}",
            eventType, userId);
    }

    private object? SanitizeDetails(object? details)
    {
        if (details == null) return null;

        var json = JsonSerializer.Serialize(details);
        var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(json);

        var sensitiveKeys = new[] { "password", "token", "secret", "key" };
        foreach (var key in sensitiveKeys)
        {
            if (dict?.ContainsKey(key) == true)
                dict[key] = "[REDACTED]";
        }

        return dict;
    }
}

CORS Configuration

// EN: Configure CORS / VI: Cấu hình CORS
var allowedOrigins = builder.Configuration
    .GetSection("Cors:AllowedOrigins")
    .Get<string[]>() ?? Array.Empty<string>();

builder.Services.AddCors(options =>
{
    options.AddPolicy("Default", policy =>
    {
        policy.WithOrigins(allowedOrigins)
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials()
            .SetPreflightMaxAge(TimeSpan.FromHours(24));
    });
});

// EN: Apply CORS / VI: Áp dụng CORS
app.UseCors("Default");

Resources / Tài Nguyên