api-v1/middleware/jwtAuthenticate.js

163 lines
5.3 KiB
JavaScript

// middleware/jwtAuthenticate.js
/**
* JWT Authentication Middleware
*
* Validates JWT tokens by calling the auth service API
* Auth service handles all token validation (signature, expiry, issuer, audience, token_version)
* Extracts and attaches user information to req.user
*
* Flow:
* 1. Extract token from Authorization header
* 2. Call auth service /auth/validate-token endpoint
* 3. Auth service validates token and returns user info
* 4. Attach user info to req.user and continue
*/
import axios from 'axios';
const AUTH_SERVICE_URL = process.env.AUTH_SERVICE_URL || 'http://localhost:3000';
const AUTH_SERVICE_TIMEOUT = parseInt(process.env.AUTH_SERVICE_TIMEOUT || '5000', 10); // 5 seconds default
/**
* Validate JWT token via auth service API
*/
async function validateTokenViaAuthService(token) {
try {
console.log(`[JWT Auth] Calling auth service to validate token...`);
const response = await axios.post(
`${AUTH_SERVICE_URL}/auth/validate-token`,
{ token },
{
timeout: AUTH_SERVICE_TIMEOUT,
headers: {
'Content-Type': 'application/json',
},
}
);
console.log(`[JWT Auth] Auth service responded with status: ${response.status}`);
if (response.data.valid === true) {
console.log(`[JWT Auth] Auth service confirmed token is valid`);
return {
valid: true,
payload: response.data.payload,
};
} else {
console.log(`[JWT Auth] Auth service reported token as invalid:`, response.data.error);
return {
valid: false,
error: response.data.error || 'Token validation failed'
};
}
} catch (err) {
// Handle different error types
if (err.response) {
// Auth service returned an error response
console.error(`[JWT Auth] ❌ Auth service validation error:`, err.response.status, err.response.data);
return {
valid: false,
error: err.response.data?.error || 'Token validation failed'
};
} else if (err.request) {
// Request was made but no response received
console.error(`[JWT Auth] ❌ Auth service unavailable:`, err.message);
return {
valid: false,
error: 'Authentication service unavailable'
};
} else {
// Error setting up request
console.error(`[JWT Auth] ❌ Error calling auth service:`, err.message);
return {
valid: false,
error: 'Failed to validate token'
};
}
}
}
/**
* JWT Authentication Middleware
*
* Calls auth service to validate token and authorize the request
*/
async function jwtAuthenticate(req, res, next) {
console.log(`[JWT Auth] Starting authentication check for ${req.method} ${req.path}`);
console.log(`[JWT Auth] Headers received:`, {
authorization: req.headers.authorization ? 'Bearer <token>' : 'MISSING',
'content-type': req.headers['content-type'],
'user-agent': req.headers['user-agent']?.substring(0, 50),
});
// Extract token from Authorization header
const authHeader = req.headers.authorization || '';
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;
if (!token) {
console.log(`[JWT Auth] ❌ FAILED: No token found in Authorization header`);
console.log(`[JWT Auth] Auth header value: "${authHeader}"`);
return res.status(401).json({
error: 'Unauthorized',
message: 'Missing Authorization header. Expected format: Authorization: Bearer <token>'
});
}
console.log(`[JWT Auth] Token found (length: ${token.length}), validating via auth service...`);
console.log(`[JWT Auth] Auth service URL: ${AUTH_SERVICE_URL}/auth/validate-token`);
// Validate token via auth service API
const validationResult = await validateTokenViaAuthService(token);
if (!validationResult.valid) {
console.log(`[JWT Auth] ❌ FAILED: Token validation failed - ${validationResult.error}`);
// Log failed authentication attempt
if (req.auditLogger) {
req.auditLogger.logFailure('authenticate', validationResult.error || 'Token validation failed');
}
return res.status(401).json({
error: 'Unauthorized',
message: 'Invalid or expired token',
details: validationResult.error
});
}
const payload = validationResult.payload;
console.log(`[JWT Auth] ✅ Token validated successfully`);
console.log(`[JWT Auth] Token payload:`, {
userId: payload.sub,
role: payload.role,
userType: payload.user_type,
phoneNumber: payload.phone_number ? '***' : null,
tokenVersion: payload.token_version,
});
// Extract user information from auth service response
req.user = {
userId: payload.sub, // Subject (user ID)
role: payload.role || 'user',
userType: payload.user_type || null,
phoneNumber: payload.phone_number || null,
tokenVersion: payload.token_version || 1,
highAssurance: payload.high_assurance || false,
// Add tenantId if present in payload (for multi-tenant apps)
tenantId: payload.tenant_id || null,
};
console.log(`[JWT Auth] ✅ User authenticated:`, {
userId: req.user.userId,
role: req.user.role,
userType: req.user.userType,
});
// Log successful authentication
if (req.auditLogger) {
req.auditLogger.logSuccess('authenticate', { userId: req.user.userId });
}
next();
}
export default jwtAuthenticate;