diff --git a/examples/better-auth/README.md b/examples/better-auth/README.md index 9a85c38..402776c 100644 --- a/examples/better-auth/README.md +++ b/examples/better-auth/README.md @@ -127,3 +127,10 @@ only after these Microsoft-specific verification checks pass. See [Microsoft's claim reference](https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims-reference) and [Better Auth's provider setup](https://better-auth.com/docs/authentication/microsoft). + +Microsoft signing keys are loaded with JOSE's remote JWKS resolver because +Microsoft's public RSA keys omit the optional `alg` member. Better Auth 1.7.3's +Microsoft key importer requires that member and otherwise throws. pgstencil pins +RS256 and the Microsoft key endpoint, retaining signature, nonce, audience, issuer, +expiry and tenant/object identity checks. Local Microsoft fixtures also omit `alg` +so this production key shape is exercised in Node and Workers tests. diff --git a/packages/auth/package.json b/packages/auth/package.json index 30f0e93..63832e1 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -24,11 +24,12 @@ "./better-auth-workers": "./src/better-auth-workers.ts" }, "dependencies": { + "better-auth": "1.7.3", "hono": "^4.13.7", + "jose": "6.2.12", "kysely": "0.29.5", "openid-client": "6.8.8", - "pgstencil": "workspace:*", - "better-auth": "1.7.3" + "pgstencil": "workspace:*" }, "engines": { "node": ">=24" diff --git a/packages/auth/src/better-auth-oauth.ts b/packages/auth/src/better-auth-oauth.ts index 8793f63..6fff004 100644 --- a/packages/auth/src/better-auth-oauth.ts +++ b/packages/auth/src/better-auth-oauth.ts @@ -6,6 +6,7 @@ import { } from 'pgstencil/diagnostics'; import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth'; import { verifyProviderIdToken } from 'better-auth/oauth2'; +import { createRemoteJWKSet } from 'jose'; import type { GithubProfile } from 'better-auth/social-providers'; import { makeSignature } from 'better-auth/crypto'; import { sql } from 'kysely'; @@ -66,6 +67,16 @@ export const verifiedOidc: BetterAuthPlugin = { : 'https://appleid.apple.com'; if (provider.idToken && 'jwks' in provider.idToken) { provider.idToken.algorithms = ['RS256']; + if (provider.id === 'microsoft') { + // Microsoft omits JWK.alg. Better Auth 1.7.3 passes that missing value + // to importJWK, which throws. JOSE selects/imports the key using the + // protected header; the verifier still permits only RS256 above. + provider.idToken.jwks = createRemoteJWKSet( + new URL( + 'https://login.microsoftonline.com/common/discovery/v2.0/keys', + ), + ); + } if (typeof provider.idToken.jwks === 'function') { const keys = provider.idToken.jwks; provider.idToken.jwks = async (...args) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66540e8..902904e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: hono: specifier: ^4.13.7 version: 4.13.7 + jose: + specifier: 6.2.12 + version: 6.2.12 kysely: specifier: 0.29.5 version: 0.29.5 diff --git a/tests/support/oauth-server.ts b/tests/support/oauth-server.ts index 28a8be4..bc23b92 100644 --- a/tests/support/oauth-server.ts +++ b/tests/support/oauth-server.ts @@ -48,7 +48,8 @@ const jwk = { export const endpointPaths: Record = { 'https://login.microsoftonline.com/common/oauth2/v2.0/token': '/microsoft/token', - 'https://login.microsoftonline.com/common/discovery/v2.0/keys': '/keys', + 'https://login.microsoftonline.com/common/discovery/v2.0/keys': + '/microsoft/keys', 'https://appleid.apple.com/.well-known/openid-configuration': '/apple/discovery', 'https://appleid.apple.com/auth/token': '/apple/token', @@ -128,6 +129,11 @@ export async function mockOAuthServer( }); } if (url.pathname === '/keys') return json({ keys: [jwk] }); + if (url.pathname === '/microsoft/keys') { + // Microsoft's real RSA signing keys omit the optional alg member. + const { alg: _alg, ...microsoftKey } = jwk; + return json({ keys: [microsoftKey] }); + } if (url.pathname.endsWith('/token')) { const form = new URLSearchParams(body); const code = form.get('code') ?? '';