From 9abcccf2ed209192bc0d2ef54edb91594a87c681 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Tue, 18 Feb 2025 09:41:35 +0000 Subject: [PATCH] Token revocation endpoint --- src/server/auth/handlers/revoke.ts | 42 ++++++++++++++++++++++++++++++ src/server/auth/provider.ts | 9 ++++++- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/server/auth/handlers/revoke.ts diff --git a/src/server/auth/handlers/revoke.ts b/src/server/auth/handlers/revoke.ts new file mode 100644 index 00000000..08b1b01e --- /dev/null +++ b/src/server/auth/handlers/revoke.ts @@ -0,0 +1,42 @@ +import { OAuthServerProvider } from "../provider.js"; +import express, { RequestHandler } from "express"; +import cors from "cors"; +import { authenticateClient } from "../middleware/clientAuth.js"; +import { OAuthTokenRevocationRequestSchema } from "../../../shared/auth.js"; + +export type RevocationHandlerOptions = { + provider: OAuthServerProvider; +}; + +export function revocationHandler({ provider }: RevocationHandlerOptions): RequestHandler { + if (!provider.revokeToken) { + throw new Error("Auth provider does not support revoking tokens"); + } + + // Nested router so we can configure middleware and restrict HTTP method + const router = express.Router(); + router.use(express.urlencoded({ extended: false })); + + // Configure CORS to allow any origin, to make accessible to web-based MCP clients + router.use(cors()); + + // Authenticate and extract client details + router.use(authenticateClient({ clientsStore: provider.clientsStore })); + + router.post("/", async (req, res) => { + let revocationRequest; + try { + revocationRequest = OAuthTokenRevocationRequestSchema.parse(req.body); + } catch (error) { + res.status(400).json({ + error: "invalid_request", + error_description: String(error), + }); + return; + } + + await provider.revokeToken!(revocationRequest); + }); + + return router; +} diff --git a/src/server/auth/provider.ts b/src/server/auth/provider.ts index 67de4bc1..5285b398 100644 --- a/src/server/auth/provider.ts +++ b/src/server/auth/provider.ts @@ -1,6 +1,6 @@ import { Response } from "express"; import { OAuthRegisteredClientsStore } from "./clients.js"; -import { OAuthClientInformationFull, OAuthTokens } from "../../shared/auth.js"; +import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from "../../shared/auth.js"; export type AuthorizationParams = { client: OAuthClientInformationFull; @@ -45,4 +45,11 @@ export interface OAuthServerProvider { * Exchanges a refresh token for an access token. */ exchangeRefreshToken(refreshToken: string, scopes?: string[]): Promise; + + /** + * Revokes an access or refresh token. If unimplemented, token revocation is not supported (not recommended). + * + * If the given token is invalid or already revoked, this method should do nothing. + */ + revokeToken?(request: OAuthTokenRevocationRequest): Promise; } \ No newline at end of file