docs: JWT authentication implementation guide and production config
- Added comprehensive JWT_AUTHENTICATION.md documentation - Covers backend (JwtAuthenticationHandler, LoginEndpoint) and frontend (useAuthApi, LoginPage) - Includes configuration for development and production modes - Security considerations and best practices - Token refresh enhancement recommendations - Testing guide and troubleshooting - API contract documentation - Deployment checklist - Added appsettings.Release.json for production JWT configuration - Placeholders for environment-specific values (JWT_KEY) - Proper listen address (0.0.0.0:5002) for containerized deployments Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
# JWT Token Authentication
|
||||
|
||||
## Overview
|
||||
|
||||
K-ArtSell Aegis uses JWT (JSON Web Token) for production authentication, replacing the Development-only header-based authentication.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Backend (ASP.NET Core)
|
||||
|
||||
**JwtAuthenticationHandler** (`src/KArtSell.Host/Security/JwtAuthenticationHandler.cs`)
|
||||
- Validates Bearer tokens from `Authorization` header
|
||||
- Verifies signature using HS256 algorithm
|
||||
- Validates issuer, audience, and expiration
|
||||
- Extracts claims: NameIdentifier, Name, Role, auth_mode
|
||||
|
||||
**LoginEndpoint** (`src/KArtSell.Host/Endpoints/Auth/LoginEndpoint.cs`)
|
||||
- `POST /api/auth/login` - Issues JWT tokens
|
||||
- Request: `{ username, password, role? }`
|
||||
- Response: `{ accessToken, expiresIn, tokenType: "Bearer" }`
|
||||
|
||||
### Frontend (Vue 3)
|
||||
|
||||
**useAuthApi** (`frontend/src/features/auth/composables/useAuthApi.ts`)
|
||||
- Token lifecycle: login, logout, getToken
|
||||
- Token persistence: localStorage
|
||||
- Expiration tracking and validation
|
||||
- Automatic cleanup on expiration
|
||||
|
||||
**LoginPage** (`frontend/src/features/auth/pages/LoginPage.vue`)
|
||||
- Username/password form
|
||||
- Token acquisition on successful login
|
||||
- Redirect to home on auth success
|
||||
|
||||
**Auth Interceptor**
|
||||
- Global fetch interceptor (setupAuthInterceptor)
|
||||
- Automatically adds `Authorization: Bearer {token}` to all requests
|
||||
- Initialized in `main.ts`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Development Mode
|
||||
|
||||
File: `appsettings.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"Authentication": {
|
||||
"Mode": "DevelopmentHeader"
|
||||
},
|
||||
"Jwt": {
|
||||
"Key": "KArtSell.Aegis.SecretKey.256Bits.v1.2026.Development.1234567890ABCDEF",
|
||||
"Issuer": "KArtSell.Aegis",
|
||||
"Audience": "KArtSell.Aegis",
|
||||
"ExpirationMinutes": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Backend**: Reads `X-KArtSell-User` and `X-KArtSell-Role` headers
|
||||
**Frontend**: Skips login, uses static headers in API requests
|
||||
|
||||
### Production Mode
|
||||
|
||||
File: `appsettings.Release.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"Authentication": {
|
||||
"Mode": "JWT"
|
||||
},
|
||||
"Jwt": {
|
||||
"Key": "${JWT_KEY}",
|
||||
"Issuer": "KArtSell.Aegis",
|
||||
"Audience": "KArtSell.Aegis",
|
||||
"ExpirationMinutes": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Environment Variable**: Set `JWT_KEY` during deployment
|
||||
- Must be at least 256 bits (32 bytes) for HMAC SHA256
|
||||
- Use cryptographically secure random string (e.g., `openssl rand -hex 32`)
|
||||
|
||||
## Usage
|
||||
|
||||
### Development
|
||||
|
||||
1. Backend starts with DevelopmentHeaderAuthenticationHandler
|
||||
2. Frontend requests include static `X-KArtSell-User`/`X-KArtSell-Role` headers
|
||||
3. No login required for testing
|
||||
|
||||
### Production
|
||||
|
||||
1. User navigates to application
|
||||
2. Router redirects to `/login`
|
||||
3. User enters credentials
|
||||
4. Frontend calls `POST /api/auth/login`
|
||||
5. Backend validates credentials and returns JWT token
|
||||
6. Frontend stores token in localStorage
|
||||
7. All subsequent requests include `Authorization: Bearer {token}`
|
||||
8. Backend validates token in each request
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Token Storage
|
||||
- Tokens stored in localStorage (accessible to XSS attacks)
|
||||
- For sensitive applications, consider using httpOnly cookies
|
||||
|
||||
### Token Expiration
|
||||
- Default: 60 minutes
|
||||
- Configurable via `Jwt:ExpirationMinutes`
|
||||
- Frontend automatically detects expiration and logs out
|
||||
|
||||
### Credential Validation
|
||||
- Current implementation accepts any non-empty username/password
|
||||
- **TODO**: Integrate with identity database for real validation
|
||||
- Add rate limiting for login attempts
|
||||
- Hash passwords with bcrypt/argon2
|
||||
|
||||
### HTTPS Only (Production)
|
||||
- Always use HTTPS in production
|
||||
- Set `Secure` flag on cookies if using cookie-based tokens
|
||||
- Implement token rotation/refresh mechanism
|
||||
|
||||
## Token Refresh (Optional Enhancement)
|
||||
|
||||
For long-running applications, implement refresh token flow:
|
||||
|
||||
1. Add `RefreshTokenEndpoint` (`POST /api/auth/refresh`)
|
||||
2. Issue longer-lived refresh tokens (1 week)
|
||||
3. Implement automatic token refresh in frontend
|
||||
4. Add refresh token rotation to prevent token reuse
|
||||
|
||||
Example implementation:
|
||||
```typescript
|
||||
// useAuthApi.ts - future enhancement
|
||||
async function refreshToken() {
|
||||
const response = await fetch('/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ refreshToken: getRefreshToken() })
|
||||
})
|
||||
// Store new token
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Backend Unit Tests
|
||||
```bash
|
||||
dotnet test KArtSell.sln -c Release
|
||||
```
|
||||
|
||||
### Frontend Unit Tests
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm test
|
||||
```
|
||||
|
||||
### Local Testing (Development Mode)
|
||||
|
||||
```bash
|
||||
# Terminal 1: SSH tunnel
|
||||
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
|
||||
|
||||
# Terminal 2: Backend
|
||||
cd src/KArtSell.Host
|
||||
dotnet run -c Debug
|
||||
|
||||
# Terminal 3: Frontend
|
||||
cd frontend
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Visit `http://localhost:5174`
|
||||
|
||||
### Local Testing (Production Mode - JWT)
|
||||
|
||||
```bash
|
||||
# Backend (Release mode)
|
||||
dotnet run -c Release --project src/KArtSell.Host
|
||||
|
||||
# Frontend (will show login)
|
||||
pnpm dev
|
||||
|
||||
# Login with any username/password
|
||||
# Will receive JWT token and be redirected to home
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 401 Unauthorized (Release Mode)
|
||||
- Missing `JWT_KEY` environment variable
|
||||
- Invalid/expired JWT token
|
||||
- Token not included in Authorization header
|
||||
|
||||
### Token Not Persisting
|
||||
- Check localStorage is enabled (not in private/incognito mode)
|
||||
- Check browser console for storage quota errors
|
||||
|
||||
### Clock Skew Issues
|
||||
- Server/client time out of sync
|
||||
- Default clock skew: 30 seconds (configurable)
|
||||
- Ensure server time is synchronized (NTP)
|
||||
|
||||
## API Contract
|
||||
|
||||
### POST /api/auth/login
|
||||
|
||||
**Request**
|
||||
```json
|
||||
{
|
||||
"username": "john_doe",
|
||||
"password": "secure_password",
|
||||
"role": "Admin" // optional
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200)**
|
||||
```json
|
||||
{
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"expiresIn": 3600,
|
||||
"tokenType": "Bearer"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (401)**
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401
|
||||
}
|
||||
```
|
||||
|
||||
### Protected Endpoints
|
||||
|
||||
**Header**
|
||||
```
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
**Invalid Token (401)**
|
||||
```
|
||||
Authorization: Bearer invalid_token
|
||||
```
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
- [ ] Set `JWT_KEY` environment variable (256+ bit secure random)
|
||||
- [ ] Configure `Jwt:Issuer` and `Jwt:Audience` to match environment
|
||||
- [ ] Update `Jwt:ExpirationMinutes` based on security requirements
|
||||
- [ ] Enable HTTPS only (redirect HTTP to HTTPS)
|
||||
- [ ] Set up database validation for credentials (not mock)
|
||||
- [ ] Implement token refresh mechanism (optional but recommended)
|
||||
- [ ] Configure rate limiting on `/api/auth/login`
|
||||
- [ ] Enable audit logging for authentication events
|
||||
- [ ] Test login flow end-to-end in staging environment
|
||||
|
||||
## References
|
||||
|
||||
- [JWT.io](https://jwt.io) - JWT debugger and documentation
|
||||
- [Microsoft Identity Model Documentation](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet)
|
||||
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
|
||||
Reference in New Issue
Block a user