Auth Service Issues
Auth Service Issues
Section titled “Auth Service Issues”The auth-service handles authentication, authorization, JWT token management, and permission validation. This guide helps diagnose and resolve common auth-service problems.
Quick Fix
Section titled “Quick Fix”# Check auth-service statusdocker ps | grep auth-service
# View auth-service logsdocker logs auth-service -f --tail 100
# Test auth endpointcurl -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"user@example.com","password":"password123"}'
# Decode JWT token to inspect claimsecho "YOUR_JWT_TOKEN" | cut -d'.' -f2 | base64 -d | jqCommon Problems
Section titled “Common Problems”1. Token Generation Failed
Section titled “1. Token Generation Failed”Symptoms:
- Login succeeds but no token returned
- Error: “Token generation failed”
- Null or undefined token in response
- Auth service logs show token generation errors
Diagnostic Steps:
# Check auth-service logs for JWT errorsdocker logs auth-service 2>&1 | grep -i "token generation"
# Verify JWT_SECRET is configureddocker exec auth-service env | grep JWT_SECRET
# Check token manager initializationdocker logs auth-service 2>&1 | grep "JWTManager"Common Causes:
A. Missing JWT_SECRET
# Check environment variablesdocker exec auth-service env | grep JWT
# Should have either:# JWT_SECRET=your-secret-key (for HS256)# OR# JWKS_URI=https://auth-provider.com/.well-known/jwks.json (for RS256)Solution:
Add JWT_SECRET to docker-compose.yml:
services: auth-service: environment: - JWT_SECRET=change-this-in-production-to-secure-random-string - JWT_ALGORITHM=HS256 # or RS256 if using JWKS - JWT_EXPIRES_IN=1h - JWT_REFRESH_EXPIRES_IN=7dB. Invalid JWT Algorithm Configuration
// Check JWTManager initialization in auth-serviceconst jwtManager = new JWTManager({ secret: process.env.JWT_SECRET, algorithm: 'HS256', // Must match JWT_ALGORITHM env var expiresIn: '1h', refreshExpiresIn: '7d'});Solution:
Ensure JWT_ALGORITHM matches signing configuration:
- Use
HS256for symmetric signing (single secret) - Use
RS256for asymmetric signing (public/private key pair)
C. User Data Missing Required Fields
// Token generation requires userId, email, permissionsthrow new Error('Token generation failed'); // From JWTManager.ts:166
// Ensure user object has required fields:const tokenPayload = { userId: user.userId, // REQUIRED email: user.email, // REQUIRED permissions: user.permissions || [] // Optional but recommended};Solution:
Verify user object before token generation:
if (!user.userId || !user.email) { throw new Error('User must have userId and email for token generation');}2. Token Validation Failures
Section titled “2. Token Validation Failures”Symptoms:
- Valid tokens rejected with “Invalid token”
- Intermittent token validation failures
- Token works in one service but not another
- “Token expired” for recently issued tokens
Diagnostic Steps:
# Decode token to check expirationTOKEN="your.jwt.token"echo $TOKEN | cut -d'.' -f2 | base64 -d | jq '.exp, .iat'
# Check current timestampdate +%s
# Compare token exp with current time# If exp < current, token is expired
# Check JWT_SECRET consistency across servicesdocker exec auth-service env | grep JWT_SECRETdocker exec api-gateway env | grep JWT_SECRET# Secrets MUST match!Common Causes:
A. JWT_SECRET Mismatch
# Each service using JWT must have SAME secret# Check all services:for service in auth-service api-gateway user-service; do echo "=== $service ===" docker exec $service env | grep JWT_SECRETdoneSolution:
Use shared environment variable or secrets management:
x-shared-jwt: &shared-jwt JWT_SECRET: ${JWT_SECRET:-change-this-in-production} JWT_ALGORITHM: ${JWT_ALGORITHM:-HS256}
services: auth-service: environment: <<: *shared-jwt
api-gateway: environment: <<: *shared-jwtB. Clock Skew Between Services
# Check time on each containerfor service in auth-service api-gateway; do echo "=== $service ===" docker exec $service datedone
# Times should be within a few seconds# Large differences cause exp/iat validation failuresSolution:
Sync container clocks or add clock skew tolerance:
// In JWT validation configconst jwtOptions = { clockTolerance: 30 // Allow 30 seconds clock skew};C. Token Format Issues
// Common token format errorsthrow new Error('Invalid token format'); // From JWTManager.ts:299
// Token must be: "Bearer <token>" in Authorization header// OR just the token string if passed directlySolution:
// Correct token extractionconst authHeader = req.headers.authorization;const token = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : authHeader;
// Validate formatif (!token || token.split('.').length !== 3) { throw new Error('Invalid JWT format');}3. Permission Denied Errors
Section titled “3. Permission Denied Errors”Symptoms:
- User cannot access operations they should have permission for
- “Permission denied” errors with correct permissions
- Inconsistent permission checking across operations
- Permission format validation errors
Diagnostic Steps:
# Decode token to check permissionsecho $TOKEN | cut -d'.' -f2 | base64 -d | jq '.permissions'
# Expected format: ["resource:action", ...]# e.g., ["users:create", "users:read", "posts:update"]
# Check handler permission requirementsgrep -r "@RequiresPermission" src/commands/ src/queries/
# Verify permission format# Must be: "resource:action" (lowercase, colon-separated)Common Permission Errors:
// From Permission.ts domain model
// Error: "Permission must be a non-empty string"throw new Error('Permission must be a non-empty string'); // Line 290
// Error: "Permission must follow 'service:action' format"throw new Error(`Permission must follow 'service:action' format, got: ${permission}`); // Line 295
// Error: "Permission service and action cannot be empty"throw new Error(`Permission service and action cannot be empty, got: ${permission}`); // Line 301
// Error: Invalid characters in permissionthrow new Error( `Permission service contains invalid characters (only lowercase letters, numbers, hyphens allowed), got: ${service}`); // Line 307
throw new Error( `Permission action contains invalid characters (only lowercase letters, numbers, hyphens allowed), got: ${action}`); // Line 313Solution:
A. Fix Permission Format
// ❌ WRONG: Various invalid formats@RequiresPermission('users-create') // Missing colon@RequiresPermission('Users:Create') // Uppercase@RequiresPermission('users:') // Empty action@RequiresPermission(':create') // Empty resource@RequiresPermission('user service:create') // Space in resource@RequiresPermission('users:create!') // Invalid character
// ✓ CORRECT: Proper format@RequiresPermission('users:create')@RequiresPermission('user-profiles:read')@RequiresPermission('api-keys:delete')B. Grant Missing Permissions
# Query user permissions from databasedocker exec postgres psql -U postgres -d platform -c \ "SELECT data->'permissions' FROM projections WHERE projection_name='user_read_model' AND id='user-123';"
# Add permission to user via commandcurl -X POST http://localhost:3000/api/users/grant-permission \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "userId": "user-123", "permission": "posts:create" }'C. Wildcard Permission Patterns
// Support for wildcard permissions// "users:*" grants all actions on users resource// "*:read" grants read on all resources// "admin:*" grants all admin permissions
// Check if user has wildcard permissionfunction hasPermission(userPermissions: string[], required: string): boolean { const [resource, action] = required.split(':');
return userPermissions.some(p => { if (p === required) return true; // Exact match if (p === `${resource}:*`) return true; // Resource wildcard if (p === `*:${action}`) return true; // Action wildcard if (p === '*:*') return true; // Super admin return false; });}4. Refresh Token Issues
Section titled “4. Refresh Token Issues”Symptoms:
- Cannot refresh access token
- “Refresh token not found or invalid”
- “Token is not a refresh token”
- Refresh token works once then fails
Diagnostic Steps:
# Check refresh token in databasedocker exec postgres psql -U postgres -d platform -c \ "SELECT token_id, user_id, expires_at, revoked_at FROM refresh_tokens WHERE user_id='user-123' ORDER BY created_at DESC LIMIT 5;"
# Decode refresh tokenecho $REFRESH_TOKEN | cut -d'.' -f2 | base64 -d | jq '{type, tokenId, userId, exp}'
# Should have: "type": "refresh"Common Causes:
A. Token Type Mismatch
// Error: "Token is not a refresh token"throw new Error('Token is not a refresh token'); // JWTManager.ts:258
// Refresh endpoint must receive refresh token, not access token// Access tokens have: "type": "access"// Refresh tokens have: "type": "refresh"Solution:
// Client should store both tokens separatelylocalStorage.setItem('accessToken', loginResponse.accessToken);localStorage.setItem('refreshToken', loginResponse.refreshToken);
// Use refresh token for refresh endpointconst response = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem('refreshToken')}` // NOT accessToken! }});B. Refresh Token Revoked
// Error: "Refresh token not found or invalid"throw new Error('Refresh token not found or invalid'); // JWTManager.ts:264
// Token may be revoked in databaseDiagnostic:
-- Check if token is revokedSELECT token_id, user_id, expires_at, revoked_at, CASE WHEN revoked_at IS NOT NULL THEN 'Revoked' WHEN expires_at < NOW() THEN 'Expired' ELSE 'Valid' END as statusFROM refresh_tokensWHERE token_id = 'token-id-from-jwt'Solution:
If token is revoked, user must re-authenticate:
// Handle refresh failure by redirecting to logintry { const newToken = await refreshAccessToken();} catch (error) { if (error.message.includes('Refresh token not found')) { // Token revoked or expired, require login redirectToLogin(); }}C. Refresh Token Expired
# Check token expirationecho $REFRESH_TOKEN | cut -d'.' -f2 | base64 -d | jq '.exp'date +%s
# If exp < current time, token expiredSolution:
Configure longer refresh token expiration:
auth-service: environment: - JWT_REFRESH_EXPIRES_IN=30d # Increase from default 7d5. External Auth Provider Issues
Section titled “5. External Auth Provider Issues”Symptoms:
- OAuth/SAML login failures
- “User ID is required” from external auth
- “Provider type is required”
- “Valid provider email is required”
- Duplicate user creation on external login
Diagnostic Steps:
# Check external auth configurationdocker exec auth-service env | grep -E "OAUTH|SAML|EXTERNAL_AUTH"
# View external auth logsdocker logs auth-service 2>&1 | grep "ExternalAuthProvider"
# Check recent external auth errorsdocker logs auth-service 2>&1 | grep -A5 "External auth failed"Common Errors from ExternalAuthProvider.ts:
// Line 199: Missing user IDthrow new Error('User ID is required');
// Line 203: Missing provider typethrow new Error('Provider type is required');
// Line 207: Missing provider IDthrow new Error('Provider ID is required');
// Line 211: Invalid emailthrow new Error('Valid provider email is required');Solution:
A. Ensure Complete External Auth Data
// External auth must provide all required fieldsconst externalAuthData = { userId: 'external-user-id', // REQUIRED providerType: 'google', // REQUIRED: 'google', 'github', 'saml', etc. providerId: 'provider-instance-id', // REQUIRED email: 'user@example.com', // REQUIRED (valid email format) name: 'John Doe', // Optional metadata: {} // Optional};
await authenticateExternalUser(externalAuthData);B. Prevent Duplicate User Creation
Recently fixed in commit 91c0c65e:
// Check if user exists before creating// Uses getInstance pattern to prevent duplicates
const user = await User.getInstance(userId);if (!user) { // Create new user const newUser = new User(userId); await newUser.register(email, hashedPassword, 'external'); await aggregateAccess.save(newUser, correlationId);}C. Configure External Auth Providers
auth-service: environment: # Google OAuth - GOOGLE_CLIENT_ID=your-client-id - GOOGLE_CLIENT_SECRET=your-client-secret - GOOGLE_CALLBACK_URL=http://localhost:3000/api/auth/google/callback
# GitHub OAuth - GITHUB_CLIENT_ID=your-client-id - GITHUB_CLIENT_SECRET=your-client-secret - GITHUB_CALLBACK_URL=http://localhost:3000/api/auth/github/callback
# SAML - SAML_ENTRY_POINT=https://sso.example.com/saml/login - SAML_ISSUER=http://localhost:3000 - SAML_CALLBACK_URL=http://localhost:3000/api/auth/saml/callback6. Policy Validation Issues
Section titled “6. Policy Validation Issues”Symptoms:
- Ownership policies fail for valid users
- Cannot edit own resources
- Admin users blocked by policies
- Policy checks return incorrect results
Diagnostic Steps:
# Check user context in requestdocker logs auth-service 2>&1 | grep "ExecutionContext"
# Verify user ID matches resource ownerdocker logs auth-service 2>&1 | grep "Policy check"
# Check admin role assignmentdocker exec postgres psql -U postgres -d platform -c \ "SELECT id, data->'roles' as roles FROM projections WHERE projection_name='user_read_model' AND id='user-123';"Solution:
A. Implement Proper Policy Checks
import { RequiresPermission } from '@banyanai/platform-base-service';import { PolicyViolationError } from '@banyanai/platform-cqrs';
@CommandHandler(UpdateUserCommand)@RequiresPermission('users:update') // Layer 1: Permission checkexport class UpdateUserHandler { async handle(command: UpdateUserCommand, context: ExecutionContext) { // Layer 2: Policy check (ownership or admin) const isOwner = command.userId === context.user.userId; const isAdmin = context.user.roles?.includes('admin');
if (!isOwner && !isAdmin) { throw new PolicyViolationError( 'OwnershipPolicy', context.user.userId, 'UpdateUser', 'User can only update their own profile unless they are an admin' ); }
// Proceed with update const user = await this.aggregateAccess.getById(command.userId); user.updateProfile(command.name, command.bio); await this.aggregateAccess.save(user, context.correlationId);
return { userId: user.userId }; }}B. Add Role-Based Policies
// Check if user has specific rolefunction hasRole(user: User, role: string): boolean { return user.roles?.includes(role) || false;}
// Policy: Admins can do anything, users can edit own resourcesif (!hasRole(context.user, 'admin') && resourceOwnerId !== context.user.userId) { throw new PolicyViolationError( 'AdminOrOwnerPolicy', context.user.userId, 'UpdateResource', 'Only admins or resource owners can perform this action' );}7. Authentication Required Errors
Section titled “7. Authentication Required Errors”Symptoms:
- “Authentication required” for all operations
- Cannot authenticate even with valid credentials
- Bypass auth not working in development
- Anonymous access blocked unexpectedly
Diagnostic Steps:
# Check if bypass auth enabled (dev only)docker exec auth-service env | grep BYPASS_AUTH
# Check authentication middlewaredocker logs auth-service 2>&1 | grep "Authentication"
# Test login endpointcurl -v -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com","password":"password123"}'Solution:
A. Enable Bypass Auth (Development Only)
# docker-compose.yml (dev environment)auth-service: environment: - BYPASS_AUTH=true # WARNING: Only use in development! - NODE_ENV=developmentB. Fix Authentication Middleware
// Ensure authentication runs before authorizationapp.use(authenticationMiddleware); // Extract and validate JWTapp.use(authorizationMiddleware); // Check permissionsapp.use(handlerMiddleware); // Execute handlerC. Allow Anonymous Operations
// Mark operations that don't require auth@QueryHandler(GetPublicPostsQuery)// NO @RequiresPermission decorator = allows anonymous accessexport class GetPublicPostsHandler { async handle(query: GetPublicPostsQuery) { // Anyone can query public posts return this.postRepository.findPublicPosts(); }}Debugging Tools
Section titled “Debugging Tools”Decode JWT Tokens
Section titled “Decode JWT Tokens”# Create helper functiondecode_jwt() { echo $1 | cut -d'.' -f2 | base64 -d 2>/dev/null | jq}
# Use itdecode_jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c2VyLTEyMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsInBlcm1pc3Npb25zIjpbInVzZXJzOnJlYWQiXSwiaWF0IjoxNjA5NDU5MjAwLCJleHAiOjE2MDk0NjI4MDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"Check Permission Database
Section titled “Check Permission Database”-- View all user permissionsSELECT id, data->>'email' as email, data->'permissions' as permissions, data->'roles' as rolesFROM projectionsWHERE projection_name = 'user_read_model'ORDER BY data->>'email';
-- Find users with specific permissionSELECT id, data->>'email' as emailFROM projectionsWHERE projection_name = 'user_read_model' AND data->'permissions' ? 'users:create';
-- Find admin usersSELECT id, data->>'email' as emailFROM projectionsWHERE projection_name = 'user_read_model' AND data->'roles' ? 'admin';Monitor Auth Operations
Section titled “Monitor Auth Operations”# Watch authentication attemptsdocker logs auth-service -f 2>&1 | grep -E "login|authenticate"
# Watch permission checksdocker logs auth-service -f 2>&1 | grep -E "permission|denied|granted"
# Watch token operationsdocker logs auth-service -f 2>&1 | grep -E "token|jwt|refresh"Test Permission Validation
Section titled “Test Permission Validation”// Test permission format validatorimport { Permission } from './domain/Permission';
function testPermission(perm: string) { try { Permission.validate(perm); console.log(`✓ Valid: ${perm}`); } catch (error) { console.log(`✗ Invalid: ${perm} - ${error.message}`); }}
testPermission('users:create'); // ✓ ValidtestPermission('users-create'); // ✗ Invalid: must use colontestPermission('Users:Create'); // ✗ Invalid: must be lowercasetestPermission('user service:create'); // ✗ Invalid: no spacesBest Practices
Section titled “Best Practices”1. Use Environment-Specific Secrets
Section titled “1. Use Environment-Specific Secrets”JWT_SECRET=dev-secret-not-secure
# .env.productionJWT_SECRET=production-secret-change-this-to-random-string-min-32-chars2. Rotate JWT Secrets
Section titled “2. Rotate JWT Secrets”// Support for multiple secrets during rotationconst jwtSecrets = [ process.env.JWT_SECRET, // Current secret process.env.JWT_SECRET_OLD // Previous secret (for validation only)];
// Validate with any valid secretfunction validateToken(token: string) { for (const secret of jwtSecrets) { try { return jwt.verify(token, secret); } catch { continue; } } throw new Error('Invalid token');}3. Implement Token Refresh Strategy
Section titled “3. Implement Token Refresh Strategy”// Auto-refresh before expirationasync function ensureValidToken() { const token = localStorage.getItem('accessToken'); const decoded = jwt.decode(token);
// Refresh if less than 5 minutes remaining const expiresIn = decoded.exp * 1000 - Date.now(); if (expiresIn < 5 * 60 * 1000) { const newToken = await refreshAccessToken(); localStorage.setItem('accessToken', newToken); }
return token;}4. Log Security Events
Section titled “4. Log Security Events”// Log all authentication and authorization eventsLogger.security('User login', { userId, email, ip: req.ip, userAgent: req.headers['user-agent'], timestamp: new Date().toISOString()});
Logger.security('Permission denied', { userId: context.user.userId, operation: 'CreateUser', required: ['users:create'], actual: context.user.permissions, timestamp: new Date().toISOString()});Related Documentation
Section titled “Related Documentation”- Authentication Concepts
- Authorization & Permissions
- Authentication Errors
- Error Catalog - Auth Errors
- JWT Best Practices
Summary
Section titled “Summary”Most auth-service issues are caused by:
- Missing or mismatched JWT_SECRET - Ensure all services use same secret
- Invalid permission format - Use
resource:actionformat (lowercase, colon-separated) - Token expiration - Implement refresh token strategy
- External auth configuration - Provide all required fields
- Policy vs Permission confusion - Layer 1 (permissions) at gateway, Layer 2 (policies) in handlers
Use docker logs auth-service and token decoding to diagnose most issues quickly.