@grebyn/toolflow-mcp-server
Version:
MCP server for managing other MCP servers - discover, install, organize into bundles, and automate with workflows. Uses StreamableHTTP transport with dual OAuth/API key authentication.
108 lines • 4.18 kB
JavaScript
/**
* Token Pass-through Handler
*
* This handler does NOT validate JWTs locally.
* It simply extracts the token and passes it to the backend API
* where validation happens with the actual JWT secret.
*/
export class TokenPassthrough {
apiEndpoint;
constructor(apiEndpoint) {
this.apiEndpoint = apiEndpoint;
}
/**
* Extract token from request and validate with backend API
*/
async extractToken(req) {
const authHeader = req.headers.authorization;
// No authorization header - anonymous request
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return { isAuthenticated: false };
}
const token = authHeader.substring(7); // Remove "Bearer " prefix
// Check for common OAuth implementation bugs
if (token.startsWith('ac_')) {
return {
isAuthenticated: false,
error: 'Authorization code received instead of JWT token. Client must exchange the authorization code for an access token first.'
};
}
// Validate token with backend API to get full user context
try {
const response = await fetch(`${this.apiEndpoint}/proxy`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
operation: 'validateOAuthToken',
params: {}
})
});
if (!response.ok) {
const errorText = await response.text();
return {
isAuthenticated: false,
error: `Token validation failed: ${errorText}`
};
}
const result = await response.json();
if (result.is_valid) {
return {
isAuthenticated: true,
token: token,
userId: result.user_id,
email: result.email,
organizationId: result.organization_id,
sessionId: result.session_id,
tokenJti: result.token_jti
};
}
else {
return {
isAuthenticated: false,
error: 'Invalid or expired OAuth token'
};
}
}
catch (error) {
return {
isAuthenticated: false,
error: `Token validation error: ${error.message}`
};
}
}
/**
* Send unauthorized response when API rejects the token
*/
sendUnauthorizedResponse(req, res, error) {
const host = req.headers.host;
const protocol = req.headers['x-forwarded-proto'] || 'http';
const baseUrl = `${protocol}://${host}`;
const protectedResourceMetadataUrl = `${baseUrl}/.well-known/oauth-protected-resource`;
const authorizationServerMetadataUrl = `${baseUrl}/.well-known/oauth-authorization-server`;
const errorResponse = {
error: 'unauthorized',
error_description: error ? error.message : 'OAuth authentication required',
resource: protectedResourceMetadataUrl
};
// MCP-compliant WWW-Authenticate header format
const wwwAuthenticate = [
'Bearer',
`realm="MCP Server"`,
`resource_metadata_uri="${protectedResourceMetadataUrl}"`,
`authorization_uri="${baseUrl}/api/oauth/authorize"`,
`discovery_uri="${authorizationServerMetadataUrl}"`
].join(', ');
res.writeHead(401, {
'WWW-Authenticate': wwwAuthenticate,
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, mcp-protocol-version',
});
res.end(JSON.stringify(errorResponse));
}
}
//# sourceMappingURL=token-passthrough.js.map