Skip to content

feat(jwt): per-audience access-token TTLs via TokenTtlPolicy SPI#12

Merged
wolpert merged 1 commit into
mainfrom
feature/per-audience-ttl-policy
May 17, 2026
Merged

feat(jwt): per-audience access-token TTLs via TokenTtlPolicy SPI#12
wolpert merged 1 commit into
mainfrom
feature/per-audience-ttl-policy

Conversation

@wolpert
Copy link
Copy Markdown
Contributor

@wolpert wolpert commented May 17, 2026

Summary

  • Introduces TokenTtlPolicy SPI in pk-auth-jwt keyed on the JWT aud claim, replacing the single JwtConfig.tokenTtl Duration. Multi-client deployments (web vs cli vs mobile) can now configure different access-token lifetimes per audience without forking PkAuthJwtIssuer.
  • Renames JwtConfig.audiencedefaultAudience. Validator accepts the union {defaultAudience} ∪ ttlPolicy.knownAudiences(), so one validator bean serves every audience the policy declares.
  • Adds optional audience field to JwtClaims. Null means "use the issuer's default audience". The validator returns the matched audience on the reconstructed JwtClaims so callers can read what the token was issued for.
  • Adapter property records (Spring PkAuthProperties.Jwt, Dropwizard PkAuthConfig.Jwt, Micronaut PkAuthConfiguration.Jwt) each gain defaultTtl + ttlsByAudience and rename their single-TTL field. Adapters build the policy via TokenTtlPolicy.fixed(...) / TokenTtlPolicy.single(...).

This is PR 1 of 3 landing on the 1.1.0 train (see docs/adr/0014-per-audience-ttl-policy.md and the rollout plan in CHANGELOG.md). PR 2 adds stateful access tokens; PR 3 adds rotating refresh tokens with family-based replay defense.

Test plan

  • ./gradlew :pk-auth-jwt:test — 28 tests pass, including 5 new cases (perAudienceTtlPolicyDispatches, audienceAbsentFromClaimsUsesDefaultAudienceAndDefaultTtl, validatorAcceptsAudiencesDeclaredByPolicy, validatorRejectsUnknownAudienceEvenIfIssuedBySameKey, allowedAudiencesIncludesDefaultPlusPolicyKnownAudiences).
  • ./gradlew check — full build green across all 14 modules including Postgres Testcontainers and DynamoDB Local integration tests, all three adapter integration tests, all three example demos.
  • Confirmed PkAuthDevModeGuardTest (which broke initially due to Spring's record-binding picking the wrong constructor) passes after removing the auxiliary constructor from PkAuthProperties.Jwt.

🤖 Generated with Claude Code

Replaces JwtConfig.tokenTtl (single Duration) with a TokenTtlPolicy SPI
keyed on the JWT aud claim, so multi-client deployments can issue
different access-token lifetimes per audience (e.g. web=15m, cli=1h)
without forking PkAuthJwtIssuer. The validator now accepts the union
of {defaultAudience} ∪ ttlPolicy.knownAudiences(). See ADR 0014.

JwtClaims gains an optional `audience` field; null falls back to
JwtConfig.defaultAudience(). Validator returns the matched audience on
the reconstructed claims so callers can read what the token was for.

Adapter property records (Spring/Dropwizard/Micronaut) gain
`defaultTtl` + `ttlsByAudience` and rename their single-TTL field.

This is the first of three PRs landing on the 1.1.0 train; the next
two add stateful access tokens (Feature 3) and rotating refresh tokens
with family-based replay defense (Feature 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@wolpert wolpert merged commit a741845 into main May 17, 2026
2 checks passed
@wolpert wolpert deleted the feature/per-audience-ttl-policy branch May 17, 2026 03:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant