163 lines
5.3 KiB
JavaScript
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;
|