better-auth
Version:
The most comprehensive authentication framework for TypeScript.
179 lines (178 loc) • 5.81 kB
JavaScript
//#region src/plugins/mcp/client/index.ts
function buildCorsHeaders(authURL, allowedOrigin) {
let origin;
if (allowedOrigin) origin = allowedOrigin;
else try {
origin = new URL(authURL).origin;
} catch {
origin = authURL;
}
return {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400"
};
}
function makeWWWAuthenticate(authURL, resource) {
return `Bearer resource_metadata="${resource ? `${resource}/.well-known/oauth-protected-resource` : `${authURL}/.well-known/oauth-protected-resource`}"`;
}
function addExposeHeader(headers, headerName) {
const exposedHeaders = headers["Access-Control-Expose-Headers"];
if (!exposedHeaders) return {
...headers,
"Access-Control-Expose-Headers": headerName
};
const alreadyExposed = exposedHeaders.split(",").some((header) => header.trim().toLowerCase() === headerName.toLowerCase());
return {
...headers,
"Access-Control-Expose-Headers": alreadyExposed ? exposedHeaders : `${exposedHeaders}, ${headerName}`
};
}
function make401Response(authURL, resource, corsHeaders) {
const wwwAuth = makeWWWAuthenticate(authURL, resource);
return Response.json({
jsonrpc: "2.0",
error: {
code: -32e3,
message: "Unauthorized: Authentication required",
"www-authenticate": wwwAuth
},
id: null
}, {
status: 401,
headers: {
...addExposeHeader(corsHeaders, "WWW-Authenticate"),
"WWW-Authenticate": wwwAuth
}
});
}
function send401Node(res, wwwAuth, message, corsHeaders) {
const body = JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32e3,
message
},
id: null
});
const existingExposedHeaders = res.get?.("Access-Control-Expose-Headers") ?? res.getHeader?.("Access-Control-Expose-Headers") ?? res.getHeader?.("access-control-expose-headers");
const headers = {
...addExposeHeader({
...corsHeaders,
...existingExposedHeaders ? { "Access-Control-Expose-Headers": Array.isArray(existingExposedHeaders) ? existingExposedHeaders.join(", ") : String(existingExposedHeaders) } : {}
}, "WWW-Authenticate"),
"WWW-Authenticate": wwwAuth
};
if (typeof res.set === "function") {
for (const [name, value] of Object.entries(headers)) res.set(name, value);
res.status?.(401).json(JSON.parse(body));
} else if (typeof res.writeHead === "function") {
res.writeHead(401, {
"Content-Type": "application/json",
...headers
});
res.end?.(body);
}
}
function createMcpAuthClient(options) {
const authURL = options.authURL.endsWith("/") ? options.authURL.slice(0, -1) : options.authURL;
const fetchFn = options.fetch ?? globalThis.fetch;
const corsHeaders = buildCorsHeaders(authURL, options.allowedOrigin);
const verifyToken = async (token) => {
try {
const response = await fetchFn(`${authURL}/mcp/get-session`, {
method: "GET",
headers: { Authorization: `Bearer ${token}` }
});
if (!response.ok) return null;
const data = await response.json();
if (!data || !data.userId) return null;
if (data.accessTokenExpiresAt && new Date(data.accessTokenExpiresAt).getTime() < Date.now()) return null;
return data;
} catch {
return null;
}
};
const handler = (fn) => {
return async (req) => {
if (req.method === "OPTIONS") return new Response(null, {
status: 204,
headers: corsHeaders
});
const authHeader = req.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) return make401Response(authURL, options.resource, corsHeaders);
const token = authHeader.slice(7);
const session = await verifyToken(token);
if (!session) return make401Response(authURL, options.resource, corsHeaders);
return fn(req, session);
};
};
const discoveryHandler = () => {
let cachedMetadata = null;
let cacheTime = 0;
const CACHE_TTL = 6e4;
return async (_req) => {
const now = Date.now();
if (cachedMetadata && now - cacheTime < CACHE_TTL) return Response.json(cachedMetadata, { headers: corsHeaders });
try {
const response = await fetchFn(`${authURL}/.well-known/oauth-authorization-server`);
if (!response.ok) return Response.json({ error: "Failed to fetch discovery metadata" }, {
status: 502,
headers: corsHeaders
});
cachedMetadata = await response.json();
cacheTime = now;
return Response.json(cachedMetadata, { headers: corsHeaders });
} catch {
return Response.json({ error: "Better Auth server unreachable" }, {
status: 502,
headers: corsHeaders
});
}
};
};
const protectedResourceHandler = (serverURL) => {
const metadata = {
resource: options.resource ?? new URL(serverURL).origin,
authorization_servers: [authURL],
bearer_methods_supported: ["header"],
scopes_supported: [
"openid",
"profile",
"email",
"offline_access"
]
};
return async (_req) => {
return Response.json(metadata, { headers: corsHeaders });
};
};
const middleware = () => {
return async (req, res, next) => {
const authHeader = req.headers?.authorization ?? req.headers?.get?.("Authorization") ?? req.get?.("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
send401Node(res, makeWWWAuthenticate(authURL, options.resource), "Unauthorized: Authentication required", corsHeaders);
return;
}
const token = authHeader.slice(7);
const session = await verifyToken(token);
if (!session) {
send401Node(res, makeWWWAuthenticate(authURL, options.resource), "Invalid or expired token", corsHeaders);
return;
}
req.mcpSession = session;
next();
};
};
return {
verifyToken,
handler,
discoveryHandler,
protectedResourceHandler,
middleware,
authURL
};
}
//#endregion
export { createMcpAuthClient };