import { describe, expect, it } from "@effect/vitest"; import { Effect } from "effect"; import { SignJWT, createLocalJWKSet, exportJWK, generateKeyPair } from "jose"; import { McpJwtVerificationError, verifyMcpAccessToken, verifyWorkOSMcpAccessToken, verifyWorkosUserManagementToken, } from "./jwt"; const issuer = "https://test-authkit.example.com"; const resource = "https://test-resource.example.com/mcp"; const otherResource = "https://other-resource.example.com/mcp"; const workosApplicationClientId = "client_workos_application_fixture"; const dynamicOAuthClientId = "client_dynamic_oauth_fixture"; const makeVerifier = async () => { const { publicKey, privateKey } = await generateKeyPair("RS256"); const jwk = await exportJWK(publicKey); const jwks = createLocalJWKSet({ keys: [{ ...jwk, kid: "test-key" }] }); const sign = (claims: Record) => new SignJWT({ org_id: "org_test", ...claims }) .setProtectedHeader({ alg: "RS256", kid: "test-key" }) .setIssuer(issuer) .setSubject("user_test") .setIssuedAt() .setExpirationTime("5m") .sign(privateKey); return { jwks, sign }; }; describe("MCP AuthKit token verification", () => { it.effect("low-level verifier rejects mismatched audience when audience is required", () => Effect.gen(function* () { const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); const token = yield* Effect.promise(() => sign({ aud: otherResource })); const error = yield* Effect.flip( verifyMcpAccessToken(token, jwks, { issuer, audience: resource, }), ); expect(error).toBeInstanceOf(McpJwtVerificationError); }), ); it.effect("low-level verifier accepts a matching resource audience", () => Effect.gen(function* () { const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); const token = yield* Effect.promise(() => sign({ aud: resource })); const verified = yield* verifyMcpAccessToken(token, jwks, { issuer, audience: resource, }); expect(verified).toEqual({ accountId: "user_test", organizationId: "org_test", }); }), ); it.effect("MCP verifier accepts WorkOS application-audience tokens", () => Effect.gen(function* () { const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); const token = yield* Effect.promise(() => sign({ aud: workosApplicationClientId, sid: "app_consent_test" }), ); const verified = yield* verifyWorkOSMcpAccessToken(token, jwks, { issuer, audience: workosApplicationClientId, }); expect(verified).toEqual({ accountId: "user_test", organizationId: "org_test", }); }), ); it.effect("MCP verifier rejects resource-audience tokens", () => Effect.gen(function* () { const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); const token = yield* Effect.promise(() => sign({ aud: resource })); const error = yield* Effect.flip( verifyWorkOSMcpAccessToken(token, jwks, { issuer, audience: workosApplicationClientId, }), ); expect(error).toBeInstanceOf(McpJwtVerificationError); }), ); it.effect("MCP verifier rejects dynamic OAuth client-audience tokens", () => Effect.gen(function* () { const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); const token = yield* Effect.promise(() => sign({ aud: dynamicOAuthClientId, sid: "app_consent_test" }), ); const error = yield* Effect.flip( verifyWorkOSMcpAccessToken(token, jwks, { issuer, audience: workosApplicationClientId, }), ); expect(error).toBeInstanceOf(McpJwtVerificationError); }), ); }); describe("access token expiry and identity boundaries", () => { for (const kind of ["mcp", "user-management"] as const) { for (const invalidClaim of ["missing-exp", "missing-iat"] as const) { it.effect(`${kind} rejects ${invalidClaim}`, () => Effect.gen(function* () { const { publicKey, privateKey } = yield* Effect.promise(() => generateKeyPair("RS256")); const jwk = yield* Effect.promise(() => exportJWK(publicKey)); const jwks = createLocalJWKSet({ keys: [{ ...jwk, kid: "expiry-key" }] }); const now = Math.floor(Date.now() / 1000); const claims = { sub: "user_test", org_id: "org_test", iss: issuer, aud: resource, ...(invalidClaim === "missing-exp" ? {} : { exp: now + 300 }), ...(invalidClaim === "missing-iat" ? {} : { iat: now }), }; const token = yield* Effect.promise(() => new SignJWT(claims) .setProtectedHeader({ alg: "RS256", kid: "expiry-key" }) .sign(privateKey), ); const error = yield* Effect.flip( kind === "mcp" ? verifyMcpAccessToken(token, jwks, { issuer, audience: resource }) : verifyWorkosUserManagementToken(token, jwks), ); expect(error).toBeInstanceOf(McpJwtVerificationError); expect(error.reason).not.toBe("system"); }), ); } it.effect(`${kind} accepts a token issued more than a day ago that has not expired`, () => Effect.gen(function* () { const { publicKey, privateKey } = yield* Effect.promise(() => generateKeyPair("RS256")); const jwk = yield* Effect.promise(() => exportJWK(publicKey)); const jwks = createLocalJWKSet({ keys: [{ ...jwk, kid: "expiry-key" }] }); const now = Math.floor(Date.now() / 1000); const token = yield* Effect.promise(() => new SignJWT({ sub: "user_test", org_id: "org_test", iss: issuer, aud: resource, iat: now - 5 * 86400, exp: now + 2 * 86400, }) .setProtectedHeader({ alg: "RS256", kid: "expiry-key" }) .sign(privateKey), ); const verified = yield* kind === "mcp" ? verifyMcpAccessToken(token, jwks, { issuer, audience: resource }) : verifyWorkosUserManagementToken(token, jwks); expect(verified).toEqual({ accountId: "user_test", organizationId: "org_test" }); }), ); it.effect(`${kind} rejects a non-string organization claim`, () => Effect.gen(function* () { const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); const token = yield* Effect.promise(() => sign({ aud: resource, org_id: { id: "org_test" } }), ); const verified = yield* kind === "mcp" ? verifyMcpAccessToken(token, jwks, { issuer, audience: resource }) : verifyWorkosUserManagementToken(token, jwks); expect(verified).toBeNull(); }), ); } });