History

fix: verify device-login tokens against the WorkOS user_management keyset

The /api JWT branch reused the MCP /oauth2 verification (AuthKit /oauth2/jwks, issuer + audience pinned), but a device-login token is a WorkOS user_management access token: signed by the SSO keyset (<workos-api>/sso/jwks/<clientId>), with no audience and an app-specific issuer. So real-WorkOS device tokens 401'd on /api (key not in the oauth2 JWKS) while login + whoami worked. Verify the user_management token against the client-scoped SSO JWKS by signature + expiry only (no issuer/audience pin); live org-membership is still re-checked. Caught by a real executor.sh login; the e2e missed it because the emulator signed all tokens with one keypair (fixed in the emulate fork to split the keysets).

Rhys Sullivan committed 4 months ago

Commit 95de8a21 parent c8eefaf4 files · +55 −38Browse files

Showing the changes to apps/cloud/src/api (1 of 4 files). Show all

Unified · Split

apps/cloud/src/api/protected-jwt-auth.node.test.ts+3−1
@@ -19,7 +19,9 @@ const makeJwt = async () => { · 18 unmodified lines
1919 const { publicKey, privateKey } = await generateKeyPair("RS256");
2020 const jwk = await exportJWK(publicKey);
2121 const jwks = createLocalJWKSet({ keys: [{ ...jwk, kid: "test-key" }] });
22− const config: JwtBearerConfig = { jwks, issuer, audience };
22+ // Device-login tokens are verified by signature alone (client-scoped SSO
23+ // JWKS); issuer/audience are not pinned.
24+ const config: JwtBearerConfig = { jwks };
2325 const sign = (claims: Record<string, unknown>, expiration: string | number = "5m") =>
2426 new SignJWT(claims)
2527 .setProtectedHeader({ alg: "RS256", kid: "test-key" })