feat(client): support custom claims in PrivateKeyJwtProvider (#1875)

This commit is contained in:
Yusufhan Saçak 2026-04-14 13:33:53 +03:00 • committed by GitHub
commit 9ed62fe7d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 1 deletions

View file

@ -233,6 +233,16 @@ export interface PrivateKeyJwtProviderOptions {
* Space-separated scopes values requested by the client.
*/
scope?: string;
/**
* 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.
*
* Useful for including additional claims that help scope the access token
* with finer granularity than what scopes alone allow.
*/
claims?: Record<string, unknown>;
}
/**
@ -277,7 +287,8 @@ export class PrivateKeyJwtProvider implements OAuthClientProvider {
subject: options.clientId,
privateKey: options.privateKey,
alg: options.algorithm,
lifetimeSeconds: options.jwtLifetimeSeconds
lifetimeSeconds: options.jwtLifetimeSeconds,
claims: options.claims
});
}

View file

@ -424,6 +424,49 @@ describe('createPrivateKeyJwtAuth', () => {
/Key for the RS256 algorithm must be one of type CryptoKey, KeyObject, or JSON Web Key/
);
});
it('includes custom claims in the signed JWT assertion', async () => {
const addClientAuth = createPrivateKeyJwtAuth({
issuer: 'client-id',
subject: 'client-id',
privateKey: 'a-string-secret-at-least-256-bits-long',
alg: 'HS256',
claims: { tenant_id: 'org-123', role: 'admin' }
});
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.tenant_id).toBe('org-123');
expect(decoded.role).toBe('admin');
expect(decoded.iss).toBe('client-id');
expect(decoded.sub).toBe('client-id');
});
it('passes custom claims through PrivateKeyJwtProvider', async () => {
const provider = new PrivateKeyJwtProvider({
clientId: 'client-id',
privateKey: 'a-string-secret-at-least-256-bits-long',
algorithm: 'HS256',
claims: { tenant_id: 'org-456' }
});
const params = new URLSearchParams();
await provider.addClientAuthentication(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.tenant_id).toBe('org-456');
expect(decoded.iss).toBe('client-id');
});
});
describe('CrossAppAccessProvider', () => {