Skip to main content

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:
1

Initial login

User provides email and password
var loginCommand = new LoginCommand
{
    Email = "[email protected]",
    Password = "SecurePassword123!"
};
var response = await mediator.Send(loginCommand);
2

Check 2FA requirement

API returns a token with requires2FA: true flag
{
  "success": true,
  "token": "eyJhbGci...",
  "requires2FA": true,
  "userId": "user-id-123"
}
3

Verify 2FA code

User receives 6-digit code via email and submits it
var verifyCommand = new ValidateTwoFactorCommand
{
    UserId = "user-id-123",
    Code = "123456"
};
var finalResponse = await mediator.Send(verifyCommand);
4

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

sub
string
required
User ID (subject claim)
email
string
required
User’s email address
jti
string
required
Unique token identifier (JWT ID)

Custom Claims

2fa_pending
boolean
required
Indicates if two-factor authentication is pending
role
string[]
Array of user roles (e.g., “Administrator”, “User”)
permission
string[]
Array of user permissions (e.g., “users.create”, “reports.view”)

Token Generation Implementation

From src/Infrastructure/Services/JwtTokenGenerator.cs:21:
JwtTokenGenerator.cs
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:
token
string
Base64-encoded cryptographically secure random token
userId
string
ID of the user who owns this token
expiresAt
DateTime
Token expiration timestamp
createdAt
DateTime
Token creation timestamp
revokedAt
DateTime?
Token revocation timestamp (null if active)
replacedByToken
string?
New token that replaced this one during refresh

Refresh Token Usage

cURL
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:
1

Request password reset

User provides their email address
POST /authentication/forgot-password
{
  "email": "[email protected]"
}
2

Receive reset token

System generates a secure token and sends it via email
Reset tokens expire after a configured time period for security
3

Submit new password

User provides reset token and new password
POST /authentication/reset-password
{
  "email": "[email protected]",
  "token": "reset-token-from-email",
  "newPassword": "NewSecurePassword123!"
}
4

Password updated

System validates token and updates password
All 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:
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

Build docs developers (and LLMs) love