diff --git a/src/server/auth/handlers/token.ts b/src/server/auth/handlers/token.ts index 1010312a..553f86ac 100644 --- a/src/server/auth/handlers/token.ts +++ b/src/server/auth/handlers/token.ts @@ -1,9 +1,8 @@ import { z } from "zod"; -import express, { RequestHandler, Response } from "express"; +import express, { RequestHandler } from "express"; import { OAuthServerProvider } from "../provider.js"; import cors from "cors"; -import { AuthorizationHandlerOptions } from "./authorize.js"; -import { OAuthClientInformationFull } from "../../../shared/auth.js"; +import { verifyChallenge } from "pkce-challenge"; export type TokenHandlerOptions = { provider: OAuthServerProvider; @@ -33,16 +32,6 @@ export function tokenHandler({ provider }: TokenHandlerOptions): RequestHandler // Configure CORS to allow any origin, to make accessible to web-based MCP clients router.use(cors()); - async function authorizationCodeGrant(client: OAuthClientInformationFull, grant: z.infer, res: Response) { - const codeChallenge = await provider.challengeForAuthorizationCode(grant.code); - // TODO - } - - async function refreshTokenGrant(client: OAuthClientInformationFull, grant: z.infer, res: Response) { - // TODO - } - - // Actual request handler router.post("/", async (req, res) => { let client_id, client_secret, grant_type; try { @@ -86,7 +75,18 @@ export function tokenHandler({ provider }: TokenHandlerOptions): RequestHandler return; } - await authorizationCodeGrant(client, grant, res); + const codeChallenge = await provider.challengeForAuthorizationCode(grant.code); + if (!(await verifyChallenge(grant.code_verifier, codeChallenge))) { + res.status(400).json({ + error: "invalid_request", + error_description: "code_verifier does not match the challenge", + }); + + return; + } + + const tokens = await provider.exchangeAuthorizationCode(grant.code); + res.status(200).json(tokens); break; } @@ -102,7 +102,9 @@ export function tokenHandler({ provider }: TokenHandlerOptions): RequestHandler return; } - await refreshTokenGrant(client, grant, res); + const scopes = grant.scope ? grant.scope.split(" ") : undefined; + const tokens = await provider.exchangeRefreshToken(grant.refresh_token, scopes); + res.status(200).json(tokens); break; } diff --git a/src/server/auth/provider.ts b/src/server/auth/provider.ts index 44f17aea..c9cf4362 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 } from "../../shared/auth.js"; +import { OAuthClientInformationFull, OAuthTokens } from "../../shared/auth.js"; export type AuthorizationParams = { client: OAuthClientInformationFull; @@ -34,4 +34,14 @@ export interface OAuthServerProvider { * Returns the `codeChallenge` that was used when the indicated authorization began. */ challengeForAuthorizationCode(authorizationCode: string): Promise; + + /** + * Exchanges an authorization code for an access token. + */ + exchangeAuthorizationCode(authorizationCode: string): Promise; + + /** + * Exchanges a refresh token for an access token. + */ + exchangeRefreshToken(refreshToken: string, scopes?: string[]): Promise; } \ No newline at end of file