From 48cddd948882853662cea64d26cea6a6fbbcde07 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Tue, 18 Feb 2025 11:25:42 +0000 Subject: [PATCH] Pass through rate limits and other options cleanly from router --- src/server/auth/router.ts | 71 ++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/src/server/auth/router.ts b/src/server/auth/router.ts index 5356023d..30e22c41 100644 --- a/src/server/auth/router.ts +++ b/src/server/auth/router.ts @@ -4,38 +4,43 @@ import { tokenHandler, TokenHandlerOptions } from "./handlers/token.js"; import { authorizationHandler, AuthorizationHandlerOptions } from "./handlers/authorize.js"; import { revocationHandler, RevocationHandlerOptions } from "./handlers/revoke.js"; import { metadataHandler } from "./handlers/metadata.js"; +import { OAuthServerProvider } from "./provider.js"; -export type MetadataOptions = { - metadata: { - /** - * The authorization server's issuer identifier, which is a URL that uses the "https" scheme and has no query or fragment components. - */ - issuerUrl: URL; +export type AuthRouterOptions = { + /** + * A provider implementing the actual authorization logic for this router. + */ + provider: OAuthServerProvider; - /** - * An optional URL of a page containing human-readable information that developers might want or need to know when using the authorization server. - */ - serviceDocumentationUrl?: URL; - }; + /** + * The authorization server's issuer identifier, which is a URL that uses the "https" scheme and has no query or fragment components. + */ + issuerUrl: URL; + + /** + * An optional URL of a page containing human-readable information that developers might want or need to know when using the authorization server. + */ + serviceDocumentationUrl?: URL; + + // Individual options per route + authorizationOptions?: Omit; + clientRegistrationOptions?: Omit; + revocationOptions?: Omit; + tokenOptions?: Omit; }; -export type AuthRouterOptions = - & AuthorizationHandlerOptions - & Omit - & MetadataOptions - & RevocationHandlerOptions - & TokenHandlerOptions; - /** * 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. * + * By default, rate limiting is applied to all endpoints to prevent abuse. + * * This router MUST be installed at the application root, like so: * * const app = express(); * app.use(mcpAuthRouter(...)); */ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler { - const issuer = options.metadata.issuerUrl; + const issuer = options.issuerUrl; // Technically RFC 8414 does not permit a localhost HTTPS exemption, but this will be necessary for ease of testing if (issuer.protocol !== "https:" && issuer.hostname !== "localhost" && issuer.hostname !== "127.0.0.1") { @@ -55,7 +60,7 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler { const metadata = { issuer: issuer.href, - service_documentation: options.metadata.serviceDocumentationUrl?.href, + service_documentation: options.serviceDocumentationUrl?.href, authorization_endpoint: new URL(authorization_endpoint, issuer).href, response_types_supported: ["code"], @@ -72,16 +77,34 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler { }; const router = express.Router(); - router.use(authorization_endpoint, authorizationHandler(options)); - router.use(token_endpoint, tokenHandler(options)); + + router.use( + authorization_endpoint, + authorizationHandler({ provider: options.provider, ...options.authorizationOptions }) + ); + + router.use( + token_endpoint, + tokenHandler({ provider: options.provider, ...options.tokenOptions }) + ); + router.use("/.well-known/oauth-authorization-server", metadataHandler(metadata)); if (registration_endpoint) { - router.use(registration_endpoint, clientRegistrationHandler({ clientsStore: options.provider.clientsStore, ...options })); + router.use( + registration_endpoint, + clientRegistrationHandler({ + clientsStore: options.provider.clientsStore, + ...options, + }) + ); } if (revocation_endpoint) { - router.use(revocation_endpoint, revocationHandler(options)); + router.use( + revocation_endpoint, + revocationHandler({ provider: options.provider, ...options.revocationOptions }) + ); } return router;