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

11 KiB

name, description, compatibility, metadata
name description compatibility metadata
security Security patterns for GoodGo platform. Use for authentication, authorization, data protection, input validation, rate limiting, or secrets management. .NET 10+, ASP.NET Core Identity, Duende IdentityServer
author version
Velik Ho 2.0

Security Patterns / Mẫu Bảo Mật

Security patterns for GoodGo microservices using ASP.NET Core Identity and Duende IdentityServer.

When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:

  • Implementing authentication (JWT, OAuth2, OIDC) / Triển khai authentication
  • Setting up authorization (RBAC, policies) / Cài đặt authorization
  • Protecting sensitive data / Bảo vệ dữ liệu nhạy cảm
  • Validating user inputs / Xác thực input từ user
  • Implementing 2FA / Triển khai xác thực 2 yếu tố
  • Managing secrets / Quản lý secrets
  • Auditing security events / Ghi log sự kiện bảo mật

Core Concepts / Khái Niệm Cốt Lõi

Security Principles / Nguyên Tắc Bảo Mật

  1. Defense in Depth - Multiple security layers
  2. Least Privilege - Minimum required permissions
  3. Fail Secure - Default to deny access
  4. Encrypt Sensitive Data - PII, tokens must be encrypted
  5. Validate All Inputs - Never trust user input
  6. Audit Everything - Log security events

ASP.NET Core Identity Stack

Component Purpose
ASP.NET Core Identity User management, password hashing
Duende IdentityServer OAuth2/OIDC provider
UserManager<T> User CRUD operations
SignInManager<T> Authentication operations
JWT Bearer Token validation

Key Patterns / Mẫu Chính

Authentication Controller / Controller Xác Thực

/// <summary>
/// EN: Authentication controller with OAuth2/OIDC support.
/// VI: Controller xác thực với OAuth2/OIDC.
/// </summary>
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/auth")]
[SwaggerTag("Authentication endpoints")]
public class AuthController : ControllerBase
{
    private readonly IMediator _mediator;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;

    public AuthController(
        IMediator mediator,
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager)
    {
        _mediator = mediator;
        _userManager = userManager;
        _signInManager = signInManager;
    }

    /// <summary>
    /// EN: Register a new user.
    /// VI: Đăng ký user mới.
    /// </summary>
    [HttpPost("register")]
    [SwaggerOperation(Summary = "Register a new user")]
    [SwaggerResponse(201, "User registered")]
    [SwaggerResponse(409, "User already exists")]
    public async Task<IActionResult> Register(
        [FromBody] RegisterUserCommand command,
        CancellationToken ct)
    {
        var result = await _mediator.Send(command, ct);
        return CreatedAtAction(
            nameof(Register), 
            ApiResponse<RegisterResult>.Ok(result));
    }
}

JWT Authorization / Phân Quyền JWT

/// <summary>
/// EN: Protected endpoint requiring authentication.
/// VI: Endpoint được bảo vệ yêu cầu xác thực.
/// </summary>
[HttpPost("change-password")]
[Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerOperation(Summary = "Change password")]
public async Task<IActionResult> ChangePassword(
    [FromBody] ChangePasswordRequest request,
    CancellationToken ct)
{
    // EN: Get user ID from JWT claims
    // VI: Lấy user ID từ JWT claims
    var userIdClaim = User.FindFirst("sub")?.Value 
        ?? User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    
    if (string.IsNullOrEmpty(userIdClaim) 
        || !Guid.TryParse(userIdClaim, out var userId))
    {
        return Unauthorized();
    }

    var command = new ChangePasswordCommand(
        userId, 
        request.CurrentPassword, 
        request.NewPassword);
    var result = await _mediator.Send(command, ct);

    if (!result.Success)
        return BadRequest(ApiResponse<object>.Fail("INVALID_PASSWORD", result.Message));

    return Ok(ApiResponse<object>.Ok(result));
}

Role-Based Authorization / Phân Quyền Theo Role

// EN: Require specific role / VI: Yêu cầu role cụ thể
[Authorize(Roles = "Admin")]
[HttpDelete("users/{userId}")]
public async Task<IActionResult> DeleteUser(Guid userId) { ... }

// EN: Require policy / VI: Yêu cầu policy
[Authorize(Policy = "CanManageUsers")]
[HttpPost("users")]
public async Task<IActionResult> CreateUser(CreateUserRequest request) { ... }

// EN: Register policy in Program.cs / VI: Đăng ký policy trong Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CanManageUsers", policy =>
        policy.RequireRole("Admin", "Manager"));
});

Password Hashing / Hash Mật Khẩu

/// <summary>
/// EN: ASP.NET Core Identity handles password hashing automatically.
/// VI: ASP.NET Core Identity tự động xử lý hash mật khẩu.
/// </summary>
public class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand, RegisterResult>
{
    private readonly UserManager<ApplicationUser> _userManager;

    public async Task<RegisterResult> Handle(
        RegisterUserCommand request, 
        CancellationToken ct)
    {
        var user = new ApplicationUser
        {
            UserName = request.Email,
            Email = request.Email,
            FullName = request.FullName
        };

        // EN: Password is automatically hashed by UserManager
        // VI: Password tự động được hash bởi UserManager
        var result = await _userManager.CreateAsync(user, request.Password);

        if (!result.Succeeded)
            throw new ValidationException(result.Errors.First().Description);

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

Two-Factor Authentication / Xác Thực 2 Yếu Tố

/// <summary>
/// EN: Enable 2FA for current user.
/// VI: Bật 2FA cho user hiện tại.
/// </summary>
[HttpPost("2fa/enable")]
[Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerOperation(Summary = "Enable 2FA")]
public async Task<IActionResult> Enable2FA(CancellationToken ct)
{
    var userId = GetUserId();
    var command = new Enable2FACommand(userId);
    var result = await _mediator.Send(command, ct);

    return Ok(ApiResponse<Enable2FAResponse>.Ok(new Enable2FAResponse
    {
        QrCodeBase64 = result.QrCodeBase64,
        ManualEntryKey = result.ManualEntryKey,
        RecoveryCodes = result.RecoveryCodes
    }));
}

Input Validation / Xác Thực Input

/// <summary>
/// EN: Request model with DataAnnotations validation.
/// VI: Request model với validation DataAnnotations.
/// </summary>
public class RegisterUserCommand : IRequest<RegisterResult>
{
    [Required]
    [EmailAddress]
    public string Email { get; set; } = string.Empty;

    [Required]
    [MinLength(8)]
    [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).+$",
        ErrorMessage = "Password must have uppercase, lowercase, digit, special char")]
    public string Password { get; set; } = string.Empty;

    [MaxLength(100)]
    public string? FullName { get; set; }
}

// EN: FluentValidation alternative / VI: Thay thế bằng FluentValidation
public class RegisterUserCommandValidator : AbstractValidator<RegisterUserCommand>
{
    public RegisterUserCommandValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty().EmailAddress();

        RuleFor(x => x.Password)
            .NotEmpty()
            .MinimumLength(8)
            .Matches("[A-Z]").WithMessage("Must have uppercase")
            .Matches("[a-z]").WithMessage("Must have lowercase")
            .Matches("[0-9]").WithMessage("Must have digit")
            .Matches("[^a-zA-Z0-9]").WithMessage("Must have special char");
    }
}

Common Mistakes / Lỗi Thường Gặp

1. Not Using Identity's Password Hasher

// ❌ BAD: Manual hashing
var hash = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(password));

// ✅ GOOD: Use UserManager
await _userManager.CreateAsync(user, password);

2. Exposing User Existence

// ❌ BAD: Reveals if user exists
if (user == null) return NotFound("User not found");
if (!valid) return BadRequest("Wrong password");

// ✅ GOOD: Generic message
if (user == null || !await _userManager.CheckPasswordAsync(user, password))
    return Unauthorized(ApiResponse<object>.Fail("INVALID_CREDENTIALS", "Invalid credentials"));

3. Missing Authorization Attribute

// ❌ BAD: No authorization
[HttpDelete("users/{id}")]
public async Task<IActionResult> DeleteUser(Guid id) { ... }

// ✅ GOOD: With authorization
[Authorize(Roles = "Admin")]
[HttpDelete("users/{id}")]
public async Task<IActionResult> DeleteUser(Guid id) { ... }

4. Logging Sensitive Data

// ❌ BAD: Logging password
_logger.LogInformation("Login attempt: {Email} {Password}", email, password);

// ✅ GOOD: Redact sensitive data
_logger.LogInformation("Login attempt: {Email}", email);

Quick Reference / Tham Chiếu Nhanh

Authentication Attributes

Attribute Purpose
[Authorize] Require authentication
[Authorize(Roles = "Admin")] Require specific role
[Authorize(Policy = "PolicyName")] Require policy
[AllowAnonymous] Allow unauthenticated

Common Status Codes

Code Meaning When to Use
401 Unauthorized Missing/invalid token
403 Forbidden Valid token, no permission
409 Conflict User already exists

JWT Claims

// EN: Get user ID from claims / VI: Lấy user ID từ claims
var userId = User.FindFirst("sub")?.Value 
    ?? User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

// EN: Check role / VI: Kiểm tra role
var isAdmin = User.IsInRole("Admin");

// EN: Get email / VI: Lấy email
var email = User.FindFirst(ClaimTypes.Email)?.Value;

Security Checklist

  • All endpoints have [Authorize] or [AllowAnonymous]
  • Passwords use ASP.NET Core Identity
  • Sensitive data is not logged
  • Error messages don't expose user existence
  • 2FA is available for users
  • JWT tokens have proper expiry
  • Secrets are in environment variables
  • Input validation with DataAnnotations/FluentValidation

Resources / Tài Nguyên