BREAKING: JWT keys are now auto-managed by Better Auth (EdDSA/Ed25519) - Remove all JWT_PRIVATE_KEY, JWT_PUBLIC_KEY, JWT_SECRET references - Keys stored in auth.jwks database table (auto-generated on first run) - Delete obsolete generate-keys.sh and generate-staging-secrets.sh scripts - Clean up legacy AUTH_*.md analysis files from root Security Improvements: - Add security_events table for audit logging - Add SecurityEventsService for tracking auth events - Enhanced security headers (HSTS, CSP, X-Frame-Options) - Rate limiting configuration Monitoring Setup: - Add auth-health-check.sh for automated testing - Add generate-dashboard.sh for HTML status dashboard - Tests: health endpoint, JWKS (EdDSA), security headers, response time - Ready for Hetzner cron deployment Documentation: - Update deployment docs with Better Auth notes - Update environment variable references - Add security improvements documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
Authentication Architecture - Validation Checklist
This checklist ensures all NestJS backend services implement authentication according to canonical patterns defined in AUTH_ARCHITECTURE_REPORT.md.
Pre-Integration Checklist
Use this before integrating auth into a new backend service.
Package Selection
- Reviewed
AUTH_ARCHITECTURE_REPORT.mdsection 9 (Integration Best Practices) - Determined whether service needs credit tracking
- No credits → Use
@manacore/shared-nestjs-auth(lightweight) - Yes, credits → Use
@mana-core/nestjs-integration(full-featured)
- No credits → Use
- Package dependency documented in
package.json
Environment Variables
-
.envfile created with required variables:MANA_CORE_AUTH_URL=http://localhost:3001NODE_ENV=development(for dev mode)DEV_BYPASS_AUTH=true(for testing without token)DEV_USER_ID=test-user-uuid(optional, for custom test user)
-
Verified
.envis NOT committed to git -
Verified
.env.exampledocuments all variables
Documentation
- Service's
CLAUDE.mdupdated with:- Auth integration pattern used (Path A or B)
- Example of
@UseGuardsusage - Example of
@CurrentUser()usage - Required environment variables listed
- Development bypass instructions
Implementation Checklist
Guard Setup
-
Guard imported from correct package:
// Path A only import { JwtAuthGuard } from '@manacore/shared-nestjs-auth'; // Path B only import { AuthGuard } from '@mana-core/nestjs-integration'; -
Guard applied globally in
main.ts:app.useGlobalGuards(new JwtAuthGuard(app.get(ConfigService))); // OR app.useGlobalGuards(new AuthGuard(options)); -
Guard applied to protected controllers:
@Controller('api') @UseGuards(JwtAuthGuard) // or AuthGuard export class MyController { ... }
Decorator Usage
-
@CurrentUser()imported from correct package:// Path A import { CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth'; // Path B import { CurrentUser } from '@mana-core/nestjs-integration'; -
Decorator used in protected route handlers:
@Get('profile') getProfile(@CurrentUser() user: CurrentUserData) { // user.userId, user.email, user.role, user.sessionId available }
Public Routes (Path B Only)
If using @mana-core/nestjs-integration:
-
@Public()decorator imported:import { Public } from '@mana-core/nestjs-integration'; -
Applied to non-protected endpoints:
@Get('health') @Public() health() { return { status: 'ok' }; } -
Verified all public routes are marked (health check, openapi, etc.)
Error Handling
- Imported
UnauthorizedExceptionfrom@nestjs/common - Auth errors return 401 status code
- Error messages don't leak implementation details
- Example error response:
{ "statusCode": 401, "message": "Unauthorized" }
API Route Validation
Route Naming Convention
- All endpoints prefixed with
/api(NestJS convention) - Global prefix set in
main.ts: ✗app.setGlobalPrefix('api/v1');(This is in mana-core-auth only)- For other backends: Regular
/apiprefix only
- For other backends: Regular
- Controllers use appropriate path prefixes
Protected Routes
For each protected route, verify:
- Decorated with
@UseGuards(JwtAuthGuard)or@UseGuards(AuthGuard) - Uses
@CurrentUser()to extract user data - Returns
401 Unauthorizedif token is missing/invalid - Doesn't require JWT parsing in handler (guard does it)
Example:
@Controller('api/favorites')
@UseGuards(JwtAuthGuard)
export class FavoriteController {
@Get()
async list(@CurrentUser() user: CurrentUserData) {
return { items: [] }; // user.userId available
}
}
Health/Status Routes
- Health endpoint does NOT require auth
- Properly decorated with
@Public()(if using Path B) - Returns
{ status: 'ok' }or similar
JWT Token Validation
Token Format
- Tokens received in
Authorization: Bearer {token}format - Guard extracts token correctly using
split(' ') - No custom token parsing in controllers
Token Claims
-
Verified token contains minimal claims only:
sub(user ID)emailrole(user | admin | service)sidorsessionId(session ID)
-
Verified token DOES NOT contain:
- Organization data (fetch via API)
- Credit balance (fetch via API)
- Customer type (derive from org presence)
- Device info (use session data)
Validation Endpoint Usage
- Guard calls
POST http://localhost:3001/api/v1/auth/validate - Validation is synchronous (guard waits for response)
- Error handling works when auth service is unreachable
Database Considerations
Schema Assumptions
- Service assumes
auth.*schema exists in main database - Or uses separate auth database (mana-core-auth default)
- Database connection URL correctly configured
User Data Storage
- User IDs stored as TEXT (matching
auth.users.idtype) - No re-hashing of passwords (auth service handles)
- Foreign keys to auth.users use TEXT type:
user_id TEXT REFERENCES auth.users(id)
Testing Checklist
Manual Token Testing
# 1. Start mana-core-auth service
pnpm dev:auth
# 2. Register user
curl -X POST http://localhost:3001/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "password123",
"name": "Test User"
}'
# 3. Login to get tokens
TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "password123"
}' | jq -r '.accessToken')
# 4. Test protected endpoint
curl http://localhost:3007/api/favorites \
-H "Authorization: Bearer $TOKEN"
# 5. Test without token (should fail)
curl http://localhost:3007/api/favorites
# Should return: 401 Unauthorized
- Login returns valid JWT token
- Protected endpoint accepts valid token
- Protected endpoint rejects missing token
- Protected endpoint rejects expired token
- Token refresh works
- User data correctly injected via
@CurrentUser()
Development Mode Testing
- Set
DEV_BYPASS_AUTH=true - Set
DEV_USER_ID=test-123(optional) - Protected endpoint works WITHOUT token
- Returns mock user data when DEV_BYPASS_AUTH enabled
Unit Tests
- Mock
ConfigServicein tests - Mock HTTP fetch for token validation
- Test guard with valid token
- Test guard with invalid token
- Test guard with missing token
- Test
@CurrentUser()decorator injection
Example test:
it('should attach user to request when token is valid', async () => {
const mockUser = { userId: 'user-123', email: 'test@example.com', role: 'user' };
const guard = new JwtAuthGuard(mockConfigService);
const result = await guard.canActivate(mockContext);
expect(result).toBe(true);
expect(request.user).toEqual(mockUser);
});
Integration Testing
With mana-core-auth Service
- Start mana-core-auth on port 3001
- Start backend service (e.g., on port 3007)
- Test real token validation flow
- Verify JWKS endpoint accessible:
curl http://localhost:3001/api/v1/auth/jwks - Verify validation endpoint accessible:
curl -X POST http://localhost:3001/api/v1/auth/validate
Multi-Service Auth
If multiple backends run simultaneously:
- All backends point to same
MANA_CORE_AUTH_URL - Token from one backend works in another
- No auth service conflicts on port 3001
- JWKS cached or refetched appropriately
Production Readiness
Environment Variables
.envfile NOT committed.env.exampledocuments all variables- All secrets retrieved from environment (not hardcoded)
NODE_ENVset toproductionin prodDEV_BYPASS_AUTHset tofalseor unset in prod
Security
- HTTPS used for auth requests (in production)
- CORS properly configured for frontend domains
- No auth tokens in logs
- No user passwords in logs
- Rate limiting enabled on auth endpoints (mana-core-auth)
Monitoring
- Logging captures auth failures
- Metrics track token validation latency
- Alerts for repeated validation failures
- JWKS fetch errors monitored
Error Messages
- Don't reveal implementation details in 401 responses
- Generic "Unauthorized" message (not "invalid signature" or "token expired")
- Development logging more verbose than production
Code Review Checklist
When reviewing auth integration PR:
- Uses only canonical guard (
JwtAuthGuardorAuthGuard) - No custom JWT parsing or validation code
- No hardcoded auth URLs (uses ConfigService)
- No plain-text tokens in logs or responses
- All protected routes have guard
- All public routes marked with
@Public()(if using Path B) @CurrentUser()used correctly- Error handling appropriate (401 for auth errors)
- Tests cover auth scenarios
- Documentation updated
Common Issues & Fixes
Issue: "No token provided" on every request
Cause: Guard not applied or incorrectly applied
Fix:
// Check main.ts - guard must be global OR per-controller
app.useGlobalGuards(new JwtAuthGuard(app.get(ConfigService)));
// Verify @UseGuards decorator present
@Controller('api')
@UseGuards(JwtAuthGuard) // Must be here if not global
export class MyController { ... }
Issue: @CurrentUser() returns undefined
Cause: Guard not running before decorator
Fix:
- Ensure guard applied to route/controller
- Ensure guard successfully attaches
request.user - Check guard implementation:
request.user = { userId, email, role, sessionId };
Issue: Dev bypass not working
Cause: Environment variables not set correctly
Fix:
# Must be EXACT strings
NODE_ENV=development # NOT 'dev' or 'test'
DEV_BYPASS_AUTH=true # String 'true', not boolean
DEV_USER_ID=test-123 # Optional, any UUID-like string
Issue: Token validation always fails
Cause: Wrong MANA_CORE_AUTH_URL or service not running
Fix:
# Verify service running
curl http://localhost:3001/api/v1/auth/jwks
# Verify config
echo $MANA_CORE_AUTH_URL # Should be http://localhost:3001
# Check logs in both services
Sign-Off
Service Name: ___________________________
Backend Port: ___________________________
Integration Path: [ ] A: Lightweight Auth [ ] B: Auth + Credits
Completed By: ___________________________ Date: ___________________________
Reviewed By: ___________________________ Date: ___________________________
Approval Checklist
- All items in Implementation Checklist verified
- All items in Testing Checklist verified
- Code review passed
- Integration test passed
- Documentation updated
- Production-ready configuration verified
Auth Architecture Approved: ___________________________ Date: ___________________________