Overview
SAPFIAI implements a comprehensive authentication system using JSON Web Tokens (JWT) with support for:
- JWT Access Tokens - Short-lived tokens for API authentication
- Refresh Tokens - Long-lived tokens for obtaining new access tokens
- Two-Factor Authentication (2FA) - Optional second layer of security
- Password Reset Flow - Secure password recovery mechanism
- Audit Logging - Track all authentication events
JWT Authentication
Deep dive into JWT token generation and validation
Two-Factor Auth
Learn about 2FA implementation and usage
Authentication Flow
Basic Login Flow
Two-Factor Authentication Flow
When 2FA is enabled, the login process includes an additional verification step:
Initial login
User provides email and passwordvar loginCommand = new LoginCommand
{
Email = "[email protected]",
Password = "SecurePassword123!"
};
var response = await mediator.Send(loginCommand);
Check 2FA requirement
API returns a token with requires2FA: true flag{
"success": true,
"token": "eyJhbGci...",
"requires2FA": true,
"userId": "user-id-123"
}
Verify 2FA code
User receives 6-digit code via email and submits itvar verifyCommand = new ValidateTwoFactorCommand
{
UserId = "user-id-123",
Code = "123456"
};
var finalResponse = await mediator.Send(verifyCommand);
Receive full access
API returns final token without 2FA restrictions{
"success": true,
"token": "eyJhbGci...",
"refreshToken": "base64-encoded-token",
"requires2FA": false
}
JWT Token Structure
JWT tokens generated by SAPFIAI include the following claims:
Standard Claims
Unique token identifier (JWT ID)
Custom Claims
Indicates if two-factor authentication is pending
Array of user roles (e.g., “Administrator”, “User”)
Array of user permissions (e.g., “users.create”, “reports.view”)
Token Generation Implementation
From src/Infrastructure/Services/JwtTokenGenerator.cs:21:
public string GenerateToken(
string userId,
string email,
IEnumerable<string>? roles = null,
IEnumerable<string>? permissions = null,
bool requiresTwoFactorVerification = false)
{
var jwtKey = _configuration["Jwt:Key"];
var jwtIssuer = _configuration["Jwt:Issuer"] ?? "SAPFIAI";
var jwtAudience = _configuration["Jwt:Audience"] ?? "SAPFIAI-Users";
var jwtExpireMinutes = int.Parse(_configuration["Jwt:ExpireMinutes"] ?? "60");
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, userId),
new(JwtRegisteredClaimNames.Email, email),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new(ClaimTypes.NameIdentifier, userId),
new("2fa_pending", requiresTwoFactorVerification.ToString().ToLower())
};
// Add roles and permissions
if (roles != null)
{
foreach (var role in roles)
claims.Add(new Claim(ClaimTypes.Role, role));
}
if (permissions != null)
{
foreach (var permission in permissions)
claims.Add(new Claim("permission", permission));
}
var token = new JwtSecurityToken(
issuer: jwtIssuer,
audience: jwtAudience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(jwtExpireMinutes),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Refresh Token Mechanism
Refresh tokens allow clients to obtain new access tokens without re-authenticating:
RefreshToken Entity
From src/Domain/Entities/RefreshToken.cs:
Base64-encoded cryptographically secure random token
ID of the user who owns this token
Token expiration timestamp
Token revocation timestamp (null if active)
New token that replaced this one during refresh
Refresh Token Usage
curl -X POST https://api.example.com/authentication/refresh-token \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "base64-encoded-token"
}'
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "new-refresh-token-base64",
"user": {
"id": "user-123",
"email": "[email protected]",
"username": "johndoe"
}
}
Token Validation
From src/Infrastructure/Services/JwtTokenGenerator.cs:105:
private ClaimsPrincipal? ValidateAndGetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]);
var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = _configuration["Jwt:Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero // No tolerance for expired tokens
}, out _);
return principal;
}
catch
{
return null;
}
}
The ClockSkew is set to TimeSpan.Zero to ensure tokens expire exactly at their specified time with no grace period.
Password Reset Flow
SAPFIAI provides a secure password reset mechanism:
Request password reset
User provides their email address Receive reset token
System generates a secure token and sends it via emailReset tokens expire after a configured time period for security
Submit new password
User provides reset token and new passwordPOST /authentication/reset-password
{
"email": "[email protected]",
"token": "reset-token-from-email",
"newPassword": "NewSecurePassword123!"
}
Password updated
System validates token and updates passwordAll existing sessions and refresh tokens are revoked for security
Security Best Practices
- Never store tokens in localStorage (vulnerable to XSS)
- Store access tokens in memory or secure cookies with HttpOnly flag
- Store refresh tokens in secure, HttpOnly cookies
- Use the SameSite attribute to prevent CSRF attacks
- Keep access tokens short-lived (15-60 minutes)
- Use longer expiration for refresh tokens (7-30 days)
- Implement token rotation on refresh
- Revoke all tokens on password change or suspicious activity
- Always use HTTPS in production
- Set Secure flag on all cookies
- Implement HSTS headers
- Use certificate pinning for mobile apps
- Implement rate limiting on authentication endpoints
- Lock accounts after multiple failed attempts
- Use CAPTCHA after several failed login attempts
- Monitor and block suspicious IP addresses
Configuration
Configure authentication in appsettings.json:
{
"Jwt": {
"Key": "your-secret-key-min-32-characters-long",
"Issuer": "SAPFIAI",
"Audience": "SAPFIAI-Users",
"ExpireMinutes": "60"
},
"RefreshToken": {
"ExpireDays": "30"
},
"Security": {
"MaxLoginAttempts": 5,
"LockoutDurationMinutes": 30
}
}
Never commit your JWT secret key to source control. Use environment variables or user secrets in development.
Next Steps
Authorization
Learn about role-based and permission-based authorization
Security Features
Explore advanced security features like IP blocking and audit logs
API Reference
View complete authentication API documentation
Configuration
Configure authentication settings for your environment