Skip to content

authExtensions docs say custom claims override reserved JWT claims, but implementation keeps reserved claims authoritative #1914

@shaun0927

Description

@shaun0927

Summary

PrivateKeyJwtProviderOptions.claims currently documents overlapping custom claims as taking precedence over the SDK's standard JWT claims, but the implementation keeps the reserved claims authoritative.

This looks like a docs / contract mismatch rather than a runtime bug or security issue.

Current docs

packages/client/src/client/authExtensions.ts says:

These are merged with the standard claims (iss, sub, aud, exp, iat, jti), with custom claims taking precedence for any overlapping keys.

Actual behavior

createPrivateKeyJwtAuth() constructs claims from { ...baseClaims, ...options.claims }, but then immediately calls:

  • .setIssuer(options.issuer)
  • .setSubject(options.subject)
  • .setAudience(audience)
  • .setIssuedAt(now)
  • .setExpirationTime(now + lifetimeSeconds)
  • .setJti(jti)

Those setters overwrite overlapping values from options.claims, so the SDK's reserved claims remain authoritative.

Minimal reproduction

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',
    tenant_id: 'org-123'
  }
});

Decoding the resulting JWT shows:

  • iss === 'client-id'
  • sub === 'client-id'
  • aud === 'https://aud.example.com'
  • tenant_id === 'org-123'

So additional custom claims are included, but overlapping reserved claims are not overridden.

Why this matters

This can mislead users into thinking they can customize reserved JWT claims through claims, when in practice only non-overlapping claims are honored.

Suggested resolution

I think the smallest fix is to align the docs/tests with the current runtime behavior:

  • clarify that additional custom claims are included
  • clarify that reserved standard claims are still set explicitly by the SDK and are not overridden
  • add a regression test showing the current behavior

If maintainers prefer the opposite behavior, that would likely deserve a separate design discussion because it changes runtime semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Nice to haves, rare edge casesbugSomething isn't workingdocumentationImprovements or additions to documentationfix proposedBot has a verified fix diff in the commentready for workEnough information for someone to start working on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions