import { createRemoteJWKSet, jwtVerify } from 'jose';
import http from 'node:http';
import https from 'node:https';
import { URL, URLSearchParams } from 'node:url';
const { OKTA_DOMAIN, OKTA_CLIENT_ID } = process.env;
if (!OKTA_DOMAIN || !OKTA_CLIENT_ID) {
console.error('Missing required env vars: OKTA_DOMAIN, OKTA_CLIENT_ID');
process.exit(1);
}
const ISSUER = `https://${OKTA_DOMAIN}/oauth2/default`;
const JWKS = createRemoteJWKSet(new URL(`${ISSUER}/v1/keys`));
const UPSTREAM = { hostname: 'localhost', port: 8001 };
// Fetch and cache Okta custom authorization server metadata
let cachedASMetadata = null;
async function fetchOktaASMetadata() {
if (cachedASMetadata) return cachedASMetadata;
const url = `${ISSUER}/.well-known/openid-configuration`;
const data = await new Promise((resolve, reject) => {
https.get(url, (res) => {
let body = '';
res.on('data', (chunk) => (body += chunk));
res.on('end', () => resolve(body));
res.on('error', reject);
}).on('error', reject);
});
cachedASMetadata = JSON.parse(data);
setTimeout(() => { cachedASMetadata = null; }, 60 * 60 * 1000);
return cachedASMetadata;
}
function getResourceUrl(req) {
const host = req.headers.host || 'localhost';
return `https://${host}`;
}
function sendJson(res, statusCode, obj) {
const body = JSON.stringify(obj, null, 2);
res.writeHead(statusCode, {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=3600',
});
res.end(body);
}
function send401(res, req) {
const resourceUrl = getResourceUrl(req);
const metadataUrl = `${resourceUrl}/.well-known/oauth-protected-resource`;
res.writeHead(401, {
'WWW-Authenticate': `Bearer resource_metadata="${metadataUrl}"`,
});
return res;
}
// Helper: read full request body
function readBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', () => resolve(body));
req.on('error', reject);
});
}
// Helper: HTTPS POST to Okta
function httpsPost(url, body, contentType) {
const parsed = new URL(url);
return new Promise((resolve, reject) => {
const options = {
hostname: parsed.hostname,
port: 443,
path: parsed.pathname + parsed.search,
method: 'POST',
headers: {
'Content-Type': contentType || 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(body),
'Accept': 'application/json',
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => resolve({ statusCode: res.statusCode, headers: res.headers, body: data }));
res.on('error', reject);
});
req.on('error', reject);
req.write(body);
req.end();
});
}
const server = http.createServer(async (req, res) => {
console.log(`${req.method} ${req.url}`);
const urlObj = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
const pathname = urlObj.pathname;
// Health check — no auth
if (pathname === '/healthz') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('ok');
return;
}
// RFC 9728 — Protected Resource Metadata
if (pathname === '/.well-known/oauth-protected-resource') {
const resourceUrl = getResourceUrl(req);
sendJson(res, 200, {
resource: resourceUrl,
authorization_servers: [resourceUrl],
bearer_methods_supported: ['header'],
scopes_supported: ['openid', 'profile', 'email'],
resource_name: 'GitHub MCP Server',
});
return;
}
// Authorization Server Metadata
// Only rewrite token_endpoint to proxy through us (to strip the RFC 8707 resource param).
// Keep authorization_endpoint pointing to Okta directly — the browser needs to reach it,
// and this ALB is internal/unreachable from the user's browser.
// Remove registration_endpoint so the SDK doesn't try dynamic client registration with Okta.
if (pathname === '/.well-known/oauth-authorization-server' || pathname === '/.well-known/openid-configuration') {
try {
const metadata = await fetchOktaASMetadata();
const resourceUrl = getResourceUrl(req);
const rewritten = {
...metadata,
// Keep authorization_endpoint pointing to Okta (browser-accessible)
// Only rewrite token_endpoint to proxy through us
token_endpoint: `${resourceUrl}/oauth/token`,
// Remove registration_endpoint to prevent dynamic client registration attempts
registration_endpoint: undefined,
};
// Clean up undefined keys
delete rewritten.registration_endpoint;
console.log('Serving AS metadata, authorization_endpoint:', rewritten.authorization_endpoint, '(Okta direct)');
console.log('Serving AS metadata, token_endpoint:', rewritten.token_endpoint, '(proxied)');
sendJson(res, 200, rewritten);
} catch (err) {
console.error('Failed to fetch Okta AS metadata:', err.message);
res.writeHead(502);
res.end('Failed to fetch authorization server metadata');
}
return;
}
// Proxy /oauth/token → Okta's token endpoint (strip resource param)
if (pathname === '/oauth/token' && req.method === 'POST') {
try {
const body = await readBody(req);
const params = new URLSearchParams(body);
// Strip 'resource' param — Okta doesn't support RFC 8707
if (params.has('resource')) {
console.log('Stripping unsupported "resource" param from token request');
params.delete('resource');
}
console.log('Proxying token request to Okta, grant_type:', params.get('grant_type'));
console.log('Token request params:', [...params.entries()].map(([k,v]) => k === 'code' || k === 'code_verifier' ? `${k}=[redacted]` : `${k}=${v}`).join('&'));
const metadata = await fetchOktaASMetadata();
const oktaTokenUrl = metadata.token_endpoint;
console.log('Forwarding to Okta token endpoint:', oktaTokenUrl);
const oktaResp = await httpsPost(oktaTokenUrl, params.toString(), 'application/x-www-form-urlencoded');
console.log('Okta token response status:', oktaResp.statusCode);
if (oktaResp.statusCode !== 200) {
console.error('Okta token error:', oktaResp.body);
} else {
console.log('Token exchange successful!');
}
// Forward Okta's response back to mcp-remote
res.writeHead(oktaResp.statusCode, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(oktaResp.body);
} catch (err) {
console.error('Token proxy error:', err.message);
res.writeHead(502);
res.end(JSON.stringify({ error: 'server_error', error_description: err.message }));
}
return;
}
// Extract Bearer token for all other requests
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
send401(res, req).end('Missing or invalid Authorization header');
return;
}
const token = auth.slice(7);
try {
await jwtVerify(token, JWKS, {
issuer: ISSUER,
audience: 'api://default',
});
} catch (err) {
console.error('JWT verification failed:', err.message);
send401(res, req).end('Invalid token');
return;
}
// Log Mcp-Session-Id for debugging Streamable HTTP session tracking
if (pathname === '/mcp') {
const sessionId = req.headers['mcp-session-id'];
console.log(`Proxying ${req.method} /mcp, Mcp-Session-Id: ${sessionId || '(none)'}`);
}
// Proxy to Supergateway (Streamable HTTP on /mcp)
// Override host header to match what supergateway expects
const upstreamHeaders = { ...req.headers, host: `${UPSTREAM.hostname}:${UPSTREAM.port}` };
const proxyReq = http.request(
{
hostname: UPSTREAM.hostname,
port: UPSTREAM.port,
path: req.url,
method: req.method,
headers: upstreamHeaders,
},
(proxyRes) => {
// Log session ID from supergateway response (assigned on first request)
if (pathname === '/mcp' && proxyRes.headers['mcp-session-id']) {
console.log(`Supergateway response Mcp-Session-Id: ${proxyRes.headers['mcp-session-id']}, status: ${proxyRes.statusCode}`);
}
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
}
);
proxyReq.on('error', (err) => {
console.error('Upstream error:', err.message);
res.writeHead(502);
res.end('Bad Gateway');
});
req.pipe(proxyReq);
});
server.listen(8000, () => {
console.log('JWT proxy listening on :8000 → upstream :8001');
});