api-v1/middleware/jwtAuthenticate.js

131 lines
3.7 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 {
const response = await axios.post(
`${AUTH_SERVICE_URL}/auth/validate-token`,
{ token },
{
timeout: AUTH_SERVICE_TIMEOUT,
headers: {
'Content-Type': 'application/json',
},
}
);
if (response.data.valid === true) {
return {
valid: true,
payload: response.data.payload,
};
} else {
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('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('Auth service unavailable:', err.message);
return {
valid: false,
error: 'Authentication service unavailable'
};
} else {
// Error setting up request
console.error('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) {
// Extract token from Authorization header
const authHeader = req.headers.authorization || '';
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;
if (!token) {
return res.status(401).json({
error: 'Unauthorized',
message: 'Missing Authorization header. Expected format: Authorization: Bearer <token>'
});
}
// Validate token via auth service API
const validationResult = await validateTokenViaAuthService(token);
if (!validationResult.valid) {
// 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;
// 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,
};
// Log successful authentication
if (req.auditLogger) {
req.auditLogger.logSuccess('authenticate', { userId: req.user.userId });
}
next();
}
export default jwtAuthenticate;