Proxy Middleware Troubleshooting
This guide covers critical troubleshooting patterns for the http-proxy-middleware implementation, including the major v3.0 breaking changes and token forwarding issues.
Critical Issue: onProxyReq Callback Not Executing
Problem Description
The most critical issue encountered was that the onProxyReq callback in http-proxy-middleware was never executing, preventing Bearer token forwarding to external APIs. This manifested as:
- 401 Unauthorized responses from external APIs
- Missing Authorization headers in proxied requests
- Debug logs showing proxy requests but no token forwarding
Root Cause Analysis
The issue was caused by a breaking change in http-proxy-middleware v3.0 that moved callback functions from direct properties to an on object structure.
Version Comparison
// ❌ BROKEN: v2.x syntax (no longer works in v3.0)
const proxy = createProxyMiddleware({
target: 'https://api.example.com',
changeOrigin: true,
// These callbacks never execute in v3.0
onProxyReq: (proxyReq, req, res) => {
console.log('This never runs!');
proxyReq.setHeader('Authorization', 'Bearer token');
},
onProxyRes: (proxyRes, req, res) => {
console.log('This never runs either!');
}
});
// ✅ WORKING: v3.0 syntax (correct implementation)
const proxy = createProxyMiddleware({
target: 'https://api.example.com',
changeOrigin: true,
// Callbacks moved to 'on' object
on: {
proxyReq: (proxyReq, req, res) => {
console.log('✅ This executes properly!');
proxyReq.setHeader('Authorization', 'Bearer token');
},
proxyRes: (proxyRes, req, res) => {
console.log('✅ This also works!');
}
}
});Working Implementation
Here's the complete working implementation for the MyNATCA Platform:
// Platform: middleware/proxy.js - WORKING v3.0 implementation
const { createProxyMiddleware } = require('http-proxy-middleware');
const createAuthenticatedProxy = (route) => {
console.log('🔧 Creating proxy for route:', route.path);
return createProxyMiddleware({
target: route.target,
changeOrigin: true,
pathRewrite: route.pathRewrite,
// ✅ CRITICAL: v3.0 syntax with 'on' object
on: {
proxyReq: (proxyReq, req, res) => {
console.log('🔧 Proxy middleware executing for:', req.path);
console.log('🎯 Target URL:', route.target);
// Session validation
if (!req.session) {
console.log('❌ No session object found');
return;
}
if (!req.session.user) {
console.log('❌ No user in session, cannot forward token');
return;
}
console.log('🔍 Session user available:', {
hasAccessToken: !!req.session.user.accessToken,
hasIdToken: !!req.session.user.idToken,
email: req.session.user.email
});
// CRITICAL: Use ID token (JWT) instead of access token (JWE)
if (req.session.user.idToken) {
proxyReq.setHeader('Authorization', `Bearer ${req.session.user.idToken}`);
console.log('🔑 Forwarding Auth0 ID token to API (JWT format)');
console.log('🔍 ID token preview:', req.session.user.idToken.substring(0, 100) + '...');
} else {
console.log('⚠️ ID token not found in session');
}
// Additional debugging
console.log('📡 Final request headers:', Object.keys(proxyReq.getHeaders()));
},
proxyRes: (proxyRes, req, res) => {
console.log('📡 Proxy response received from:', req.path);
console.log('📊 Response status:', proxyRes.statusCode);
// Enable CORS for all proxied responses
const origin = req.headers.origin;
if (origin) {
proxyRes.headers['Access-Control-Allow-Origin'] = origin;
proxyRes.headers['Access-Control-Allow-Credentials'] = 'true';
}
// Additional CORS headers
proxyRes.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS';
proxyRes.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
if (proxyRes.statusCode >= 400) {
console.log('⚠️ Proxy response error:', proxyRes.statusCode);
}
},
error: (err, req, res) => {
console.error('❌ Proxy error:', err);
res.status(500).json({
success: false,
error: {
code: 'PROXY_ERROR',
message: 'Failed to proxy request'
}
});
}
}
});
};
module.exports = { createAuthenticatedProxy };Token Format Issues
Problem: JWE vs JWT Token Mismatch
External APIs like MyNATCA require JWT format tokens, but Auth0 provides both JWE and JWT tokens:
// Auth0 provides two types of tokens:
{
accessToken: "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIi...", // JWE (encrypted)
idToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCI..." // JWT (signed)
}Solution: Use ID Token for API Forwarding
// ❌ WRONG: Using accessToken (JWE encrypted format)
proxyReq.setHeader('Authorization', `Bearer ${req.session.user.accessToken}`);
// Results in: 401 Unauthorized from external APIs
// ✅ CORRECT: Using idToken (JWT signed format)
proxyReq.setHeader('Authorization', `Bearer ${req.session.user.idToken}`);
// Results in: Successful API authenticationToken Format Identification
// Helper function to identify token format
const identifyTokenFormat = (token) => {
try {
const header = JSON.parse(
Buffer.from(token.split('.')[0], 'base64').toString()
);
if (header.alg === 'dir' && header.enc) {
return 'JWE (encrypted)';
} else if (header.alg && header.typ === 'JWT') {
return 'JWT (signed)';
}
} catch (error) {
return 'Unknown format';
}
return 'Invalid token';
};
// Usage in debugging
console.log('Access token format:', identifyTokenFormat(req.session.user.accessToken));
console.log('ID token format:', identifyTokenFormat(req.session.user.idToken));Debugging Proxy Issues
Comprehensive Debug Logging
// Platform: Debug middleware for proxy issues
const debugProxy = (req, res, next) => {
console.log('\n=== PROXY DEBUG START ===');
console.log('🌐 Request URL:', req.method, req.path);
console.log('🔍 Request headers:', req.headers);
// Session debugging
console.log('📦 Session debugging:');
console.log(' - Session exists:', !!req.session);
console.log(' - Session ID:', req.session?.id);
if (req.session?.user) {
console.log(' - User email:', req.session.user.email);
console.log(' - Access token exists:', !!req.session.user.accessToken);
console.log(' - ID token exists:', !!req.session.user.idToken);
if (req.session.user.idToken) {
try {
const payload = JSON.parse(
Buffer.from(req.session.user.idToken.split('.')[1], 'base64').toString()
);
console.log(' - Token expires:', new Date(payload.exp * 1000));
console.log(' - Token issuer:', payload.iss);
console.log(' - Token audience:', payload.aud);
console.log(' - NATCA ID:', payload['https://my.natca.org/natca_id']);
} catch (error) {
console.log(' - Token parsing error:', error.message);
}
}
} else {
console.log(' - No user in session');
}
console.log('=== PROXY DEBUG END ===\n');
next();
};
// Apply to proxy routes
app.use('/api/mynatca/*', debugProxy);Proxy Route Testing
# Test proxy route with curl
curl -v -H "Cookie: platform.session=your-session-cookie" \
http://localhost:1300/api/mynatca/Member/12985
# Expected output:
# > GET /api/mynatca/Member/12985 HTTP/1.1
# > Host: localhost:1300
# > Cookie: platform.session=...
#
# < HTTP/1.1 200 OK
# < Content-Type: application/jsonNetwork Analysis
// Platform: Network request logging
const logNetworkRequests = (req, res, next) => {
const startTime = Date.now();
// Log request
console.log(`📤 Outgoing: ${req.method} ${req.path}`);
// Override res.end to log response
const originalEnd = res.end;
res.end = function(chunk, encoding) {
const duration = Date.now() - startTime;
console.log(`📥 Response: ${res.statusCode} (${duration}ms)`);
if (res.statusCode >= 400) {
console.log(`❌ Error response body:`, chunk?.toString());
}
originalEnd.call(this, chunk, encoding);
};
next();
};Common Troubleshooting Scenarios
Scenario 1: "Proxy requests return 401 Unauthorized"
Diagnosis Steps:
- Verify callback syntax (v3.0
onobject) - Check token format (JWT vs JWE)
- Confirm session contains user data
- Validate token expiration
Resolution:
// Check callback execution
on: {
proxyReq: (proxyReq, req, res) => {
console.log('✅ Callback executing'); // Should appear in logs
if (req.session.user?.idToken) {
proxyReq.setHeader('Authorization', `Bearer ${req.session.user.idToken}`);
console.log('🔑 Token forwarded successfully');
}
}
}Scenario 2: "Sessions not persisting across requests"
Diagnosis Steps:
- Check Redis connection
- Verify session configuration
- Confirm cookie settings
- Test session middleware order
Resolution:
// Test Redis connectivity
redisClient.ping((err, result) => {
console.log('Redis ping:', err || result);
});
// Verify session middleware is before proxy middleware
app.use(sessionMiddleware); // Must be first
app.use('/api/proxy', proxy); // Proxy middleware after sessionScenario 3: "CORS errors on proxied responses"
Diagnosis Steps:
- Check origin headers
- Verify CORS configuration
- Confirm credentials inclusion
Resolution:
on: {
proxyRes: (proxyRes, req, res) => {
// Dynamic CORS based on request origin
const origin = req.headers.origin;
if (origin && allowedOrigins.includes(origin)) {
proxyRes.headers['Access-Control-Allow-Origin'] = origin;
proxyRes.headers['Access-Control-Allow-Credentials'] = 'true';
}
}
}Scenario 4: "Proxy middleware not loading routes"
Diagnosis Steps:
- Check route registration order
- Verify database connectivity
- Confirm route configuration format
Resolution:
// Ensure routes are loaded before server starts
const setupProxyRoutes = async () => {
try {
const { data: routes, error } = await supabase
.from('proxy_routes')
.select('*');
if (error) {
console.error('Failed to load routes:', error);
return;
}
routes.forEach(route => {
const proxy = createAuthenticatedProxy(route);
app.use(route.path, proxy);
console.log(`✅ Route registered: ${route.path} -> ${route.target}`);
});
} catch (error) {
console.error('Route setup error:', error);
}
};
// Call before starting server
setupProxyRoutes().then(() => {
app.listen(1300, () => {
console.log('Server started with proxy routes loaded');
});
});Version Migration Guide
Upgrading from v2.x to v3.0
-
Update package.json:
{ "dependencies": { "http-proxy-middleware": "^3.0.0" } } -
Update callback syntax:
// Before (v2.x) const proxy = createProxyMiddleware({ onProxyReq: handler, onProxyRes: handler }); // After (v3.0) const proxy = createProxyMiddleware({ on: { proxyReq: handler, proxyRes: handler } }); -
Test all proxy routes:
- Verify callbacks execute
- Check token forwarding
- Confirm CORS headers
- Validate error handling
Verification Checklist
- Callbacks execute (check console logs)
- Authorization headers forwarded
- CORS headers present in responses
- Error handling works correctly
- Session data accessible in callbacks
- External APIs return 200 (not 401)
This troubleshooting guide covers the most critical issues encountered with proxy middleware implementation. The v3.0 syntax change and token format discovery were essential breakthroughs for successful API integration.