17 KiB
17 KiB
Security Patterns - Detailed Reference
Detailed code examples for security patterns in ASP.NET Core.
Table of Contents
- Authentication with Identity
- JWT Configuration
- Authorization Policies
- Password Hashing
- Two-Factor Authentication
- Input Validation
- Rate Limiting
- Global Exception Handler
- Audit Logging
- 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");