Skip to content

Commit bb49189

Browse files
committed
Router for all MCP auth endpoints + behaviors
1 parent a6e78b7 commit bb49189

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

src/server/auth/router.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import express, { RequestHandler } from "express";
2+
import { clientRegistrationHandler, ClientRegistrationHandlerOptions } from "./handlers/register.js";
3+
import { tokenHandler, TokenHandlerOptions } from "./handlers/token.js";
4+
import { authorizationHandler, AuthorizationHandlerOptions } from "./handlers/authorize.js";
5+
import { revocationHandler, RevocationHandlerOptions } from "./handlers/revoke.js";
6+
import { metadataHandler } from "./handlers/metadata.js";
7+
8+
export type MetadataOptions = {
9+
metadata: {
10+
/**
11+
* The authorization server's issuer identifier, which is a URL that uses the "https" scheme and has no query or fragment components.
12+
*/
13+
issuerUrl: URL;
14+
15+
/**
16+
* An optional URL of a page containing human-readable information that developers might want or need to know when using the authorization server.
17+
*/
18+
serviceDocumentationUrl?: URL;
19+
};
20+
};
21+
22+
export type AuthRouterOptions =
23+
& AuthorizationHandlerOptions
24+
& Omit<ClientRegistrationHandlerOptions, "clientsStore">
25+
& MetadataOptions
26+
& RevocationHandlerOptions
27+
& TokenHandlerOptions;
28+
29+
/**
30+
* Installs standard MCP authorization endpoints, including dynamic client registration and token revocation (if supported). Also advertises standard authorization server metadata, for easier discovery of supported configurations by clients.
31+
*
32+
* This router MUST be installed at the application root, like so:
33+
*
34+
* const app = express();
35+
* app.use(mcpAuthRouter(...));
36+
*/
37+
export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler {
38+
const issuer = options.metadata.issuerUrl;
39+
40+
// Technically RFC 8414 does not permit a localhost HTTPS exemption, but this will be necessary for ease of testing
41+
if (issuer.protocol !== "https:" && issuer.hostname !== "localhost" && issuer.hostname !== "127.0.0.1") {
42+
throw new Error("Issuer URL must be HTTPS");
43+
}
44+
if (issuer.hash) {
45+
throw new Error("Issuer URL must not have a fragment");
46+
}
47+
if (issuer.search) {
48+
throw new Error("Issuer URL must not have a query string");
49+
}
50+
51+
const authorization_endpoint = "/authorize";
52+
const token_endpoint = "/token";
53+
const registration_endpoint = options.provider.clientsStore.registerClient ? "/register" : undefined;
54+
const revocation_endpoint = options.provider.revokeToken ? "/revoke" : undefined;
55+
56+
const metadata = {
57+
issuer: issuer.href,
58+
service_documentation: options.metadata.serviceDocumentationUrl?.href,
59+
60+
authorization_endpoint: new URL(authorization_endpoint, issuer).href,
61+
response_types_supported: ["code"],
62+
code_challenge_methods_supported: ["S256"],
63+
64+
token_endpoint: new URL(token_endpoint, issuer).href,
65+
token_endpoint_auth_methods_supported: ["client_secret_post"],
66+
grant_types_supported: ["authorization_code", "refresh_token"],
67+
68+
revocation_endpoint: revocation_endpoint ? new URL(revocation_endpoint, issuer).href : undefined,
69+
revocation_endpoint_auth_methods_supported: revocation_endpoint ? ["client_secret_post"] : undefined,
70+
71+
registration_endpoint: registration_endpoint ? new URL(registration_endpoint, issuer).href : undefined,
72+
};
73+
74+
const router = express.Router();
75+
router.use(authorization_endpoint, authorizationHandler(options));
76+
router.use(token_endpoint, tokenHandler(options));
77+
router.use("/.well-known/oauth-authorization-server", metadataHandler(metadata));
78+
79+
if (registration_endpoint) {
80+
router.use(registration_endpoint, clientRegistrationHandler({ clientsStore: options.provider.clientsStore, ...options }));
81+
}
82+
83+
if (revocation_endpoint) {
84+
router.use(revocation_endpoint, revocationHandler(options));
85+
}
86+
87+
return router;
88+
}

0 commit comments

Comments
 (0)