6.6 KiB
Token Validation Architecture
Overview
The Core project implements comprehensive JWT token validation across three token types:
- Access Tokens: Short-lived (1 hour) tokens for API authentication
- Refresh Tokens: Long-lived (21 days) tokens for obtaining new access tokens
- Confirmation Tokens: Short-lived (30 minutes) tokens for email confirmation
Components
Infrastructure Layer
ITokenInfrastructure
Low-level JWT operations.
Methods:
GenerateJwt()- Creates signed JWT tokensValidateJwtAsync()- Validates token signature, expiration, and format
Implementation: JwtInfrastructure.cs
- Uses Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler
- Algorithm: HS256 (HMAC-SHA256)
- Validates token lifetime, signature, and well-formedness
Service Layer
ITokenValidationService
High-level token validation with context (token type, user extraction).
Methods:
ValidateAccessTokenAsync(string token)- Validates access tokensValidateRefreshTokenAsync(string token)- Validates refresh tokensValidateConfirmationTokenAsync(string token)- Validates confirmation tokens
Returns: ValidatedToken record containing:
UserId(Guid)Username(string)Principal(ClaimsPrincipal) - Full JWT claims
Implementation: TokenValidationService.cs
- Reads token secrets from environment variables
- Extracts and validates claims (Sub, UniqueName)
- Throws
UnauthorizedExceptionon validation failure
ITokenService
Token generation (existing service extended).
Methods:
GenerateAccessToken(UserAccount)- Creates 1-hour access tokenGenerateRefreshToken(UserAccount)- Creates 21-day refresh tokenGenerateConfirmationToken(UserAccount)- Creates 30-minute confirmation token
Integration Points
ConfirmationService
Flow:
- Receives confirmation token from user
- Calls
TokenValidationService.ValidateConfirmationTokenAsync() - Extracts user ID from validated token
- Calls
AuthRepository.ConfirmUserAccountAsync()to update database - Returns confirmation result
RefreshTokenService
Flow:
- Receives refresh token from user
- Calls
TokenValidationService.ValidateRefreshTokenAsync() - Retrieves user account via
AuthRepository.GetUserByIdAsync() - Issues new access and refresh tokens via
TokenService - Returns new token pair
AuthController
Endpoints:
POST /api/auth/register- Register new userPOST /api/auth/login- Authenticate userPOST /api/auth/confirm?token=...- Confirm emailPOST /api/auth/refresh- Refresh access token
Validation Security
Token Secrets
Three independent secrets enable:
- Key rotation - Rotate each secret type independently
- Isolation - Compromise of one secret doesn't affect others
- Different expiration - Different token types can expire at different rates
Environment Variables:
ACCESS_TOKEN_SECRET=... # Signs 1-hour access tokens
REFRESH_TOKEN_SECRET=... # Signs 21-day refresh tokens
CONFIRMATION_TOKEN_SECRET=... # Signs 30-minute confirmation tokens
Validation Checks
Each token is validated for:
- Signature Verification - Token must be signed with correct secret
- Expiration - Token must not be expired (checked against current time)
- Claims Presence - Required claims (Sub, UniqueName) must be present
- Claims Format - UserId claim must be a valid GUID
Error Handling
Validation failures return HTTP 401 Unauthorized:
- Invalid signature → "Invalid token"
- Expired token → "Invalid token" (message doesn't reveal reason for security)
- Missing claims → "Invalid token"
- Malformed claims → "Invalid token"
Token Lifecycle
Access Token Lifecycle
- Generation: During login (1-hour validity)
- Usage: Included in Authorization header on API requests
- Validation: Validated on protected endpoints
- Expiration: Token becomes invalid after 1 hour
- Refresh: Use refresh token to obtain new access token
Refresh Token Lifecycle
- Generation: During login (21-day validity)
- Storage: Client-side (secure storage)
- Usage: Posted to
/api/auth/refreshendpoint - Validation: Validated by RefreshTokenService
- Rotation: New refresh token issued on successful refresh
- Expiration: Token becomes invalid after 21 days
Confirmation Token Lifecycle
- Generation: During user registration (30-minute validity)
- Delivery: Emailed to user in confirmation link
- Usage: User clicks link, token posted to
/api/auth/confirm - Validation: Validated by ConfirmationService
- Completion: User account marked as confirmed
- Expiration: Token becomes invalid after 30 minutes
Testing
Unit Tests
TokenValidationService.test.cs
- Happy path: Valid token extraction
- Error cases: Invalid, expired, malformed tokens
- Missing/invalid claims scenarios
RefreshTokenService.test.cs
- Successful refresh with valid token
- Invalid/expired refresh token rejection
- Non-existent user handling
ConfirmationService.test.cs
- Successful confirmation with valid token
- Token validation failures
- User not found scenarios
BDD Tests (Reqnroll)
TokenRefresh.feature
- Successful token refresh
- Invalid/expired token rejection
- Missing token validation
Confirmation.feature
- Successful email confirmation
- Expired/tampered token rejection
- Missing token validation
AccessTokenValidation.feature
- Protected endpoint access token validation
- Invalid/expired access token rejection
- Token type mismatch (refresh used as access token)
Future Enhancements
Stretch Goals
-
Middleware for Access Token Validation
- Automatically validate access tokens on protected routes
- Populate HttpContext.User from token claims
- Return 401 for invalid/missing tokens
-
Token Blacklisting
- Implement token revocation (e.g., on logout)
- Store blacklisted tokens in cache/database
- Check blacklist during validation
-
Refresh Token Rotation Strategy
- Detect token reuse (replay attacks)
- Automatically invalidate entire token chain on reuse
- Log suspicious activity
-
Structured Logging
- Log token validation attempts
- Track failed validation reasons
- Alert on repeated validation failures (brute force detection)