Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/JReyna217/AutoLog/llms.txt

Use this file to discover all available pages before exploring further.

AutoLog issues HMAC-SHA256 signed JWTs using .NET’s System.IdentityModel.Tokens.Jwt library. The token lifecycle is split into two tiers: short-lived access tokens that authorize individual API calls, and long-lived refresh tokens that are stored in the PostgreSQL database and rotated on every use. Both token types are returned together as a TokenResponseDto on login and on each successful refresh.

JwtSettings Configuration Reference

All JWT behaviour is controlled by the JwtSettings block in appsettings.json. In production, override every value through environment variables so that secrets never live in source control.
KeyDefault (appsettings.json)Environment VariableDescription
Secret(empty — must be set)JwtSettings__SecretHMAC-SHA256 signing key. Must be at least 32 characters. Use a randomly generated string of 64+ characters in production.
IssuerAutoLog.APIJwtSettings__IssuerThe iss claim value embedded in every access token. The bearer middleware validates this on every request.
AudienceAutoLog.AngularClientJwtSettings__AudienceThe aud claim value embedded in every access token. Must match the ValidAudience set in Program.cs.
ExpiryMinutes15JwtSettings__ExpiryMinutesLifetime of the access token in minutes. Decrease for stricter security; increase only if UX demands it.
RefreshExpiryDays7JwtSettings__RefreshExpiryDaysLifetime of the refresh token in days. After expiry the user must log in again.
The corresponding appsettings.json block looks like this:
{
  "JwtSettings": {
    "Secret": "",
    "Issuer": "AutoLog.API",
    "Audience": "AutoLog.AngularClient",
    "ExpiryMinutes": 15,
    "RefreshExpiryDays": 7
  }
}

Access Token Claims

Every access token generated by JwtService.GenerateAccessToken carries the following claims:
ClaimJWT Registered NameValueDescription
subJwtRegisteredClaimNames.SubUser’s integer ID (string)Standard subject identifier — uniquely identifies the authenticated user.
emailJwtRegisteredClaimNames.EmailUser’s email addressUsed for display purposes and identity resolution.
nameJwtRegisteredClaimNames.NameUser’s full nameThe FullName value stored on the User entity.
UserIdCustom claimUser’s integer ID (string)Duplicate of sub added for convenient extraction in middleware and the global exception handler without relying on the standard sub name.
The claim generation code in JwtService.cs:
var claims = new List<Claim>
{
    new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
    new Claim(JwtRegisteredClaimNames.Email, user.Email),
    new Claim(JwtRegisteredClaimNames.Name, user.FullName),
    new Claim("UserId", user.Id.ToString()) // Custom claim for our GlobalExceptionHandler
};

Token Rotation

Calling POST /api/auth/refresh triggers a full token rotation cycle. AutoLog does not reuse refresh tokens — each call produces an entirely new pair. The rotation logic in RefreshTokenCommandHandler:
  1. The expired access token is passed through JwtService.GetPrincipalFromExpiredToken, which validates the token’s signature and claims but explicitly skips lifetime validation (ValidateLifetime = false).
  2. The UserId custom claim is extracted to look up the user in the database.
  3. The stored RefreshToken value is compared to the token sent in the request. If they don’t match, or the RefreshTokenExpiryTime has passed, the request is rejected with 401.
  4. A new access token and a new refresh token are generated.
  5. The user record is updated with the new RefreshToken and a new RefreshTokenExpiryTime (DateTime.UtcNow.AddDays(RefreshExpiryDays)), then persisted to the database.
  6. The new TokenResponseDto is returned to the client.
user.RefreshToken = newRefreshToken;
user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(refreshDays);
await userRepository.UpdateAsync(user, cancellationToken);

return new TokenResponseDto { AccessToken = newAccessToken, RefreshToken = newRefreshToken };
Refresh tokens are opaque 64-byte cryptographically random values generated with RandomNumberGenerator.Create() and Base64-encoded. They are never signed JWTs and carry no embedded claims.

Token Validation Parameters

The following validation parameters are registered in Program.cs for the JwtBearerDefaults.AuthenticationScheme and are applied to every incoming request on protected endpoints:
options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = true,
    ValidateAudience = true,
    ValidateLifetime = true,
    ValidateIssuerSigningKey = true,
    ValidIssuer = builder.Configuration["JwtSettings:Issuer"],
    ValidAudience = builder.Configuration["JwtSettings:Audience"],
    IssuerSigningKey = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Secret"]!))
};
ParameterValueEffect
ValidateIssuertrueRejects tokens whose iss claim does not match JwtSettings:Issuer.
ValidateAudiencetrueRejects tokens whose aud claim does not match JwtSettings:Audience.
ValidateLifetimetrueRejects tokens whose exp claim is in the past.
ValidateIssuerSigningKeytrueRejects tokens whose HMAC-SHA256 signature cannot be verified with the configured secret.
During token refresh, ValidateLifetime is intentionally set to false inside JwtService.GetPrincipalFromExpiredToken — this is the only code path where an expired access token is accepted. The algorithm is still verified to be HmacSha256; any token with a different algorithm header is rejected with a 401.

Security Considerations

The JWT secret (JwtSettings:Secret) must be at least 32 characters long for HMAC-SHA256. Shorter keys will cause a runtime exception when the application starts. Never commit a real secret to source control — use environment variables or a secrets manager in all non-development environments.
Generate a cryptographically strong 64-character secret for production using OpenSSL:
openssl rand -base64 48
This produces a 64-character Base64 string (48 random bytes). Set the output as the JwtSettings__Secret environment variable on your server or container.
Access tokens are intentionally short-lived (15 minutes by default) to minimise exposure if a token is intercepted. Adjust JwtSettings:ExpiryMinutes upward only when UX requirements genuinely demand it, and pair any increase with stricter transport security (HTTPS-only, HSTS).

Build docs developers (and LLMs) love