{
"JwtSettings": {
"SecretKey": "{{AZURE_KEY_VAULT}}",
"Issuer": "https://api.erp.com",
"Audience": "https://api.erp.com",
"TokenExpiration": 60,
"RefreshTokenDays": 7
}
}
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["JwtSettings:SecretKey"])),
ValidateIssuer = true,
ValidIssuer = config["JwtSettings:Issuer"],
ValidateAudience = true,
ValidAudience = config["JwtSettings:Audience"]
};
});
-- Users -> UserRoles -> Roles -> RolePermissions -> Permissions
CREATE TABLE Users (
UserId INT PRIMARY KEY IDENTITY,
Username VARCHAR(50) UNIQUE,
Email VARCHAR(100) UNIQUE,
PasswordHash VARCHAR(500),
IsActive BIT DEFAULT 1
);
CREATE TABLE Roles (
RoleId INT PRIMARY KEY IDENTITY,
RoleName VARCHAR(50) UNIQUE
);
CREATE TABLE Permissions (
PermissionId INT PRIMARY KEY IDENTITY,
PermissionName VARCHAR(100) UNIQUE,
Module VARCHAR(50)
);
builder.Services.AddAuthorization(options => {
options.AddPolicy("Accounting.View", p => p.RequireClaim("permission", "Accounting.View"));
options.AddPolicy("Admin", p => p.RequireRole("Administrator"));
});
[ApiController]
[Authorize]
public class AccountingController : ControllerBase
{
[HttpGet]
[Authorize(Policy = "Accounting.View")]
public async Task<IActionResult> GetData() { }
}
public interface IPasswordService
{
string HashPassword(string password);
bool VerifyPassword(string password, string hash);
}
// Use BCrypt or Argon2
public class PasswordService : IPasswordService
{
private readonly PasswordHasher<object> _hasher = new();
public string HashPassword(string password)
=> _hasher.HashPassword(null, password);
public bool VerifyPassword(string password, string hash)
=> _hasher.VerifyHashedPassword(null, hash, password) == PasswordVerificationResult.Success;
}
// Minimum 8 chars, 1 upper, 1 lower, 1 digit
RuleFor(x => x.Password)
.MinimumLength(8)
.Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$");
public interface IEncryptionService
{
string Encrypt(string text);
string Decrypt(string cipherText);
}
// Store keys in Azure Key Vault
// Use for sensitive fields only (NationalId, BankAccount, etc.)
Security Headers
app.Use(async (context, next) => {
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Remove("Server");
await next();
});
builder.Services.AddCors(options => {
options.AddDefaultPolicy(policy => {
policy.WithOrigins("https://app.erp.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
builder.Services.AddRateLimiter(options => {
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
context => RateLimitPartition.GetFixedWindowLimiter(
context.User?.Identity?.Name ?? context.Connection.RemoteIpAddress?.ToString(),
_ => new FixedWindowRateLimiterOptions {
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
});
// ✅ SAFE - Always use parameters
var sql = "SELECT * FROM Users WHERE Id = @Id";
await connection.QueryAsync<User>(sql, new { Id = userId });
// ❌ NEVER - String concatenation
var sql = $"SELECT * FROM Users WHERE Id = {userId}"; // SQL Injection!
CREATE TABLE AuditLogs (
LogId INT IDENTITY PRIMARY KEY,
UserId VARCHAR(50),
Action VARCHAR(100),
EntityType VARCHAR(50),
EntityId VARCHAR(50),
Timestamp DATETIME2,
IPAddress VARCHAR(45)
);
public interface IAuditService
{
Task LogAsync(string action, string entityType, string entityId);
}
| Component |
Library/Tool |
Purpose |
| Authentication |
JWT Bearer |
Token-based auth |
| Password Hash |
BCrypt/Argon2 |
Secure password storage |
| Encryption |
AES-256 |
Sensitive data protection |
| SQL |
Dapper/EF Core |
Parameterized queries |
| Secrets |
Azure Key Vault |
Secure key storage |
| Monitoring |
Application Insights |
Logging & alerts |
- Never store passwords in plain text
- Never use string concatenation for SQL
- Never expose sensitive data in logs
- Never hardcode secrets in code
- Never trust user input without validation
- Never use HTTP in production