From f38581ff4ad75499361b790f4d9cfbc6e73b8653 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Tue, 18 Aug 2026 00:18:48 +0900 Subject: [PATCH] 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 --- docs/JWT_AUTHENTICATION.md | 266 +++++++++++++++++++++ src/KArtSell.Host/appsettings.Release.json | 26 ++ 2 files changed, 292 insertions(+) create mode 100644 docs/JWT_AUTHENTICATION.md create mode 100644 src/KArtSell.Host/appsettings.Release.json diff --git a/docs/JWT_AUTHENTICATION.md b/docs/JWT_AUTHENTICATION.md new file mode 100644 index 00000000..a7bc11ef --- /dev/null +++ b/docs/JWT_AUTHENTICATION.md @@ -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) diff --git a/src/KArtSell.Host/appsettings.Release.json b/src/KArtSell.Host/appsettings.Release.json new file mode 100644 index 00000000..7cabb3c7 --- /dev/null +++ b/src/KArtSell.Host/appsettings.Release.json @@ -0,0 +1,26 @@ +{ + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://0.0.0.0:5002" + } + } + }, + "Authentication": { + "Mode": "JWT" + }, + "Jwt": { + "Key": "${JWT_KEY}", + "Issuer": "KArtSell.Aegis", + "Audience": "KArtSell.Aegis", + "ExpirationMinutes": 60 + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft.AspNetCore": "Warning" + } + } + } +}