Skip to content

Commit e99fb6c

Browse files
committed
Align custom-claim docs with actual JWT behavior
The client auth helpers advertise overlapping custom claims as taking precedence over reserved JWT claims, but the implementation re-applies the reserved claims through SignJWT setters. This change narrows the scope to documentation and a focused regression test so the published contract matches shipped behavior without expanding API surface or changing runtime semantics. Constraint: Upstream review guidance prefers small, spec-aware fixes backed by concrete evidence Rejected: Change runtime behavior so custom claims override reserved claims | would alter shipped semantics and widen scope beyond a docs fix Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep reserved JWT claims authoritative unless maintainers explicitly decide to change runtime behavior in a separate design discussion Tested: packages/client/authExtensions test suite; client package typecheck; client package lint/prettier Not-tested: full monorepo test matrix
1 parent 9ed62fe commit e99fb6c

2 files changed

Lines changed: 32 additions & 2 deletions

File tree

packages/client/src/client/authExtensions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,9 @@ export interface PrivateKeyJwtProviderOptions {
236236

237237
/**
238238
* Optional custom claims to include in the JWT assertion.
239-
* These are merged with the standard claims (`iss`, `sub`, `aud`, `exp`, `iat`, `jti`),
240-
* with custom claims taking precedence for any overlapping keys.
239+
* These are merged with the standard claims (`iss`, `sub`, `aud`, `exp`, `iat`, `jti`).
240+
* Additional custom claims are included, but overlapping standard claims are
241+
* still set explicitly by the SDK and therefore are not overridden.
241242
*
242243
* Useful for including additional claims that help scope the access token
243244
* with finer granularity than what scopes alone allow.

packages/client/test/client/authExtensions.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,35 @@ describe('createPrivateKeyJwtAuth', () => {
448448
expect(decoded.sub).toBe('client-id');
449449
});
450450

451+
it('does not allow custom claims to override standard JWT claims', async () => {
452+
const addClientAuth = createPrivateKeyJwtAuth({
453+
issuer: 'client-id',
454+
subject: 'client-id',
455+
privateKey: 'a-string-secret-at-least-256-bits-long',
456+
alg: 'HS256',
457+
audience: 'https://aud.example.com',
458+
claims: {
459+
iss: 'override-issuer',
460+
sub: 'override-subject',
461+
aud: 'https://override.example.com',
462+
tenant_id: 'org-123'
463+
}
464+
});
465+
466+
const params = new URLSearchParams();
467+
await addClientAuth(new Headers(), params, 'https://auth.example.com/token', undefined);
468+
469+
const assertion = params.get('client_assertion');
470+
expect(assertion).toBeTruthy();
471+
472+
const jose = await import('jose');
473+
const decoded = jose.decodeJwt(assertion!);
474+
expect(decoded.iss).toBe('client-id');
475+
expect(decoded.sub).toBe('client-id');
476+
expect(decoded.aud).toBe('https://aud.example.com');
477+
expect(decoded.tenant_id).toBe('org-123');
478+
});
479+
451480
it('passes custom claims through PrivateKeyJwtProvider', async () => {
452481
const provider = new PrivateKeyJwtProvider({
453482
clientId: 'client-id',

0 commit comments

Comments
 (0)