docs: update configuration and documentation for token validation

This commit is contained in:
Aaron Po
2026-02-28 23:19:12 -05:00
parent c20be03f89
commit c5571fcf47
5 changed files with 292 additions and 26 deletions

View File

@@ -58,38 +58,52 @@ built from components.
**Implementation**: See `DefaultSqlConnectionFactory.cs`
### JWT Authentication
### JWT Authentication Secrets (Backend)
The backend uses separate secrets for different token types to enable independent key rotation and validation isolation.
```bash
JWT_SECRET=your-secret-key-minimum-32-characters-required
# Access token secret (1-hour tokens)
ACCESS_TOKEN_SECRET=<generated-secret> # Signs short-lived access tokens
# Refresh token secret (21-day tokens)
REFRESH_TOKEN_SECRET=<generated-secret> # Signs long-lived refresh tokens
# Confirmation token secret (30-minute tokens)
CONFIRMATION_TOKEN_SECRET=<generated-secret> # Signs email confirmation tokens
```
- **Required**: Yes
- **Minimum Length**: 32 characters (enforced)
- **Purpose**: Signs JWT tokens for user authentication
- **Algorithm**: HS256 (HMAC-SHA256)
**Security Requirements**:
**Generate Secret**:
- Each secret should be minimum 32 characters
- Recommend 127+ characters for production
- Generate using cryptographically secure random functions
- Never reuse secrets across token types or environments
- Rotate secrets periodically in production
**Generate Secrets**:
```bash
# macOS/Linux
# macOS/Linux - Generate 127-character base64 secret
openssl rand -base64 127
# Windows PowerShell
[Convert]::ToBase64String((1..127 | %{Get-Random -Max 256}))
```
**Additional JWT Settings** (appsettings.json):
**Token Expiration**:
```json
{
"Jwt": {
"ExpirationMinutes": 60,
"Issuer": "biergarten-api",
"Audience": "biergarten-users"
}
}
```
- **Access tokens**: 1 hour
- **Refresh tokens**: 21 days
- **Confirmation tokens**: 30 minutes
(Defined in `TokenServiceExpirationHours` class)
**JWT Implementation**:
- **Algorithm**: HS256 (HMAC-SHA256)
- **Handler**: Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler
- **Validation**: Token signature, expiration, and malformed token checks
### Migration Control
@@ -274,8 +288,10 @@ touch .env.local
| `DB_CONNECTION_STRING` | ✓ | | | Yes\* | Alternative to components |
| `DB_TRUST_SERVER_CERTIFICATE` | ✓ | | ✓ | No | Defaults to True |
| `SA_PASSWORD` | | | ✓ | Yes | SQL Server container |
| **Authentication (Backend)** |
| `JWT_SECRET` | ✓ | | ✓ | Yes | Min 32 chars |
| **Authentication (Backend - JWT)** |
| `ACCESS_TOKEN_SECRET` | ✓ | | ✓ | Yes | Access token secret |
| `REFRESH_TOKEN_SECRET` | ✓ | | ✓ | Yes | Refresh token secret |
| `CONFIRMATION_TOKEN_SECRET` | ✓ | | ✓ | Yes | Confirmation token secret |
| **Authentication (Frontend)** |
| `CONFIRMATION_TOKEN_SECRET` | | ✓ | | Yes | Email confirmation |
| `RESET_PASSWORD_TOKEN_SECRET` | | ✓ | | Yes | Password reset |
@@ -339,8 +355,10 @@ DB_NAME=Biergarten
DB_USER=sa
DB_PASSWORD=Dev_Password_123!
# JWT
JWT_SECRET=development-secret-key-at-least-32-characters-long-recommended-longer
# JWT Authentication Secrets
ACCESS_TOKEN_SECRET=<generated-with-openssl>
REFRESH_TOKEN_SECRET=<generated-with-openssl>
CONFIRMATION_TOKEN_SECRET=<generated-with-openssl>
# Migration
CLEAR_DATABASE=true
@@ -363,8 +381,6 @@ BASE_URL=http://localhost:3000
NODE_ENV=development
# Authentication
CONFIRMATION_TOKEN_SECRET=<generated-with-openssl>
RESET_PASSWORD_TOKEN_SECRET=<generated-with-openssl>
SESSION_SECRET=<generated-with-openssl>
# Database (current Prisma setup)

205
docs/token-validation.md Normal file
View File

@@ -0,0 +1,205 @@
# 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](Infrastructure.Jwt/ITokenInfrastructure.cs)
Low-level JWT operations.
**Methods:**
- `GenerateJwt()` - Creates signed JWT tokens
- `ValidateJwtAsync()` - Validates token signature, expiration, and format
**Implementation:** [JwtInfrastructure.cs](Infrastructure.Jwt/JwtInfrastructure.cs)
- Uses Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler
- Algorithm: HS256 (HMAC-SHA256)
- Validates token lifetime, signature, and well-formedness
### Service Layer
#### [ITokenValidationService](Service.Auth/ITokenValidationService.cs)
High-level token validation with context (token type, user extraction).
**Methods:**
- `ValidateAccessTokenAsync(string token)` - Validates access tokens
- `ValidateRefreshTokenAsync(string token)` - Validates refresh tokens
- `ValidateConfirmationTokenAsync(string token)` - Validates confirmation tokens
**Returns:** `ValidatedToken` record containing:
- `UserId` (Guid)
- `Username` (string)
- `Principal` (ClaimsPrincipal) - Full JWT claims
**Implementation:** [TokenValidationService.cs](Service.Auth/TokenValidationService.cs)
- Reads token secrets from environment variables
- Extracts and validates claims (Sub, UniqueName)
- Throws `UnauthorizedException` on validation failure
#### [ITokenService](Service.Auth/ITokenService.cs)
Token generation (existing service extended).
**Methods:**
- `GenerateAccessToken(UserAccount)` - Creates 1-hour access token
- `GenerateRefreshToken(UserAccount)` - Creates 21-day refresh token
- `GenerateConfirmationToken(UserAccount)` - Creates 30-minute confirmation token
### Integration Points
#### [ConfirmationService](Service.Auth/IConfirmationService.cs)
**Flow:**
1. Receives confirmation token from user
2. Calls `TokenValidationService.ValidateConfirmationTokenAsync()`
3. Extracts user ID from validated token
4. Calls `AuthRepository.ConfirmUserAccountAsync()` to update database
5. Returns confirmation result
#### [RefreshTokenService](Service.Auth/RefreshTokenService.cs)
**Flow:**
1. Receives refresh token from user
2. Calls `TokenValidationService.ValidateRefreshTokenAsync()`
3. Retrieves user account via `AuthRepository.GetUserByIdAsync()`
4. Issues new access and refresh tokens via `TokenService`
5. Returns new token pair
#### [AuthController](API.Core/Controllers/AuthController.cs)
**Endpoints:**
- `POST /api/auth/register` - Register new user
- `POST /api/auth/login` - Authenticate user
- `POST /api/auth/confirm?token=...` - Confirm email
- `POST /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:**
```bash
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:
1. **Signature Verification** - Token must be signed with correct secret
2. **Expiration** - Token must not be expired (checked against current time)
3. **Claims Presence** - Required claims (Sub, UniqueName) must be present
4. **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
1. **Generation**: During login (1-hour validity)
2. **Usage**: Included in Authorization header on API requests
3. **Validation**: Validated on protected endpoints
4. **Expiration**: Token becomes invalid after 1 hour
5. **Refresh**: Use refresh token to obtain new access token
### Refresh Token Lifecycle
1. **Generation**: During login (21-day validity)
2. **Storage**: Client-side (secure storage)
3. **Usage**: Posted to `/api/auth/refresh` endpoint
4. **Validation**: Validated by RefreshTokenService
5. **Rotation**: New refresh token issued on successful refresh
6. **Expiration**: Token becomes invalid after 21 days
### Confirmation Token Lifecycle
1. **Generation**: During user registration (30-minute validity)
2. **Delivery**: Emailed to user in confirmation link
3. **Usage**: User clicks link, token posted to `/api/auth/confirm`
4. **Validation**: Validated by ConfirmationService
5. **Completion**: User account marked as confirmed
6. **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
1. **Middleware for Access Token Validation**
- Automatically validate access tokens on protected routes
- Populate HttpContext.User from token claims
- Return 401 for invalid/missing tokens
2. **Token Blacklisting**
- Implement token revocation (e.g., on logout)
- Store blacklisted tokens in cache/database
- Check blacklist during validation
3. **Refresh Token Rotation Strategy**
- Detect token reuse (replay attacks)
- Automatically invalidate entire token chain on reuse
- Log suspicious activity
4. **Structured Logging**
- Log token validation attempts
- Track failed validation reasons
- Alert on repeated validation failures (brute force detection)