From 2430289047a1677493d0e14037a6ec1e07110ad6 Mon Sep 17 00:00:00 2001 From: mukunda katta Date: Fri, 15 May 2026 08:05:29 -0700 Subject: [PATCH] docs: clarify private key jwt claim precedence --- packages/client/src/client/authExtensions.ts | 4 +-- .../client/test/client/authExtensions.test.ts | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/client/src/client/authExtensions.ts b/packages/client/src/client/authExtensions.ts index cb476c12fd..272cb37f84 100644 --- a/packages/client/src/client/authExtensions.ts +++ b/packages/client/src/client/authExtensions.ts @@ -236,8 +236,8 @@ export interface PrivateKeyJwtProviderOptions { /** * Optional custom claims to include in the JWT assertion. - * These are merged with the standard claims (`iss`, `sub`, `aud`, `exp`, `iat`, `jti`), - * with custom claims taking precedence for any overlapping keys. + * These are merged with the standard claims (`iss`, `sub`, `aud`, `exp`, `iat`, `jti`). + * Standard claims are set explicitly by the SDK and cannot be overridden by custom claims. * * Useful for including additional claims that help scope the access token * with finer granularity than what scopes alone allow. diff --git a/packages/client/test/client/authExtensions.test.ts b/packages/client/test/client/authExtensions.test.ts index 16c3ea33e1..5bf5445014 100644 --- a/packages/client/test/client/authExtensions.test.ts +++ b/packages/client/test/client/authExtensions.test.ts @@ -448,6 +448,41 @@ describe('createPrivateKeyJwtAuth', () => { expect(decoded.sub).toBe('client-id'); }); + it('keeps standard JWT claims authoritative over custom claim overlaps', async () => { + const addClientAuth = createPrivateKeyJwtAuth({ + issuer: 'client-id', + subject: 'client-id', + privateKey: 'a-string-secret-at-least-256-bits-long', + alg: 'HS256', + audience: 'https://aud.example.com', + claims: { + iss: 'override-issuer', + sub: 'override-subject', + aud: 'https://override.example.com', + exp: 1, + iat: 1, + jti: 'override-jti', + tenant_id: 'org-123' + } + }); + + const params = new URLSearchParams(); + await addClientAuth(new Headers(), params, 'https://auth.example.com/token', undefined); + + const assertion = params.get('client_assertion'); + expect(assertion).toBeTruthy(); + + const jose = await import('jose'); + const decoded = jose.decodeJwt(assertion!); + expect(decoded.iss).toBe('client-id'); + expect(decoded.sub).toBe('client-id'); + expect(decoded.aud).toBe('https://aud.example.com'); + expect(decoded.exp).not.toBe(1); + expect(decoded.iat).not.toBe(1); + expect(decoded.jti).not.toBe('override-jti'); + expect(decoded.tenant_id).toBe('org-123'); + }); + it('passes custom claims through PrivateKeyJwtProvider', async () => { const provider = new PrivateKeyJwtProvider({ clientId: 'client-id',