import { Data, Effect, Result } from "effect"; import { jwtVerify, type JWTVerifyGetKey } from "jose"; import { JOSEError, JWKSInvalid, JWKSTimeout, JWTExpired } from "jose/errors"; export type VerifiedToken = { /** The WorkOS account ID (user ID). */ accountId: string; /** The WorkOS organization ID, if the session has org context. */ organizationId: string | null; }; export class McpJwtVerificationError extends Data.TaggedError("McpJwtVerificationError")<{ readonly cause: unknown; readonly reason: "expired" | "invalid" | "system"; }> {} const classifyJwtVerificationError = (cause: unknown): McpJwtVerificationError => new McpJwtVerificationError({ cause, reason: cause instanceof JWTExpired ? "expired" : cause instanceof JWKSTimeout || cause instanceof JWKSInvalid || !(cause instanceof JOSEError) ? "system" : "invalid", }); const isExpectedJwtVerificationError = (error: McpJwtVerificationError): boolean => error.reason === "expired" || error.reason === "invalid"; const withJwtVerificationSpan = ( effect: Effect.Effect, ): Effect.Effect => effect.pipe( Effect.result, Effect.flatMap((outcome) => Effect.gen(function* () { if (Result.isSuccess(outcome)) { yield* Effect.annotateCurrentSpan({ "mcp.auth.jwt_verify.outcome": "verified" }); return outcome; } yield* Effect.annotateCurrentSpan({ "mcp.auth.jwt_verify.outcome": outcome.failure.reason, }); return isExpectedJwtVerificationError(outcome.failure) ? outcome : yield* Effect.fail(outcome.failure); }), ), Effect.withSpan("mcp.auth.jwt_verify"), Effect.flatMap((outcome) => Result.isSuccess(outcome) ? Effect.succeed(outcome.success) : Effect.fail(outcome.failure), ), ); export const verifyMcpAccessToken = ( token: string, jwks: JWTVerifyGetKey, options: { readonly issuer: string; readonly audience: string; }, ) => Effect.gen(function* () { const { payload } = yield* Effect.tryPromise({ try: () => jwtVerify(token, jwks, { issuer: options.issuer, audience: options.audience, }), catch: classifyJwtVerificationError, }).pipe(withJwtVerificationSpan); if (!payload.sub) return null; return { accountId: payload.sub, organizationId: (payload.org_id as string | undefined) ?? null, } satisfies VerifiedToken; }); export const verifyWorkOSMcpAccessToken = ( token: string, jwks: JWTVerifyGetKey, options: { readonly issuer: string; readonly audience: string; }, ) => Effect.gen(function* () { const verified = yield* verifyMcpAccessToken(token, jwks, { issuer: options.issuer, audience: options.audience, }); yield* Effect.annotateCurrentSpan({ "mcp.auth.audience_mode": "workos_client", }); return verified; });