Skip to content

Harden JWKS access assertion validation with replay protection#139

Merged
jalexw merged 1 commit into
mainfrom
claude/bold-tesla-yhori5
Jun 12, 2026
Merged

Harden JWKS access assertion validation with replay protection#139
jalexw merged 1 commit into
mainfrom
claude/bold-tesla-yhori5

Conversation

@jalexw

@jalexw jalexw commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements hardened validation for JWKS access assertions with replay protection and comprehensive claim verification. The auth server now enforces strict claim requirements, validates token age, and prevents assertion replay using Redis-backed jti tracking.

Key Changes

Core Validation Hardening

  • New constants module (packages/jwt/src/JwksAccessProofToken/constants.ts): Defines JWKS_ACCESS_PROOF_TOKEN_MAX_AGE (60s) and JWKS_ACCESS_PROOF_TOKEN_REQUIRED_CLAIMS (exp, iat, jti, aud, iss) as shared validation rules
  • Updated token creation (createJwksAccessProofToken.ts):
    • Generates unique jti for every assertion (prevents replay)
    • Sets exp to 60 seconds from iat (matches maxTokenAge window)
    • Includes all required claims: api_server_id, sub, iss, aud, iat, nbf, exp, jti
  • Enhanced verification (verifyJwksAccessProofToken.ts): Uses jose's requiredClaims and maxTokenAge options to enforce strict validation

Replay Protection

  • Redis-backed jti tracking (auth-server/src/app/api/jwks/[audience]/verifyJwksAccessAssertion.ts):
    • Each accepted jti is stored in Redis with 120s TTL (twice the 60s acceptance window)
    • Uses Redis SET NX to ensure single-use semantics: replayed assertions are rejected
    • Validates jti shape (string, non-empty, ≤255 chars) before using as Redis key component

Comprehensive Test Coverage

  • Unit tests (JwksAccessProofToken.test.ts):
    • Validates all required claims are present and correctly formatted
    • Tests unique jti generation per assertion
    • Negative tests for missing claims (exp, iat, jti)
    • Validates expiration and staleness rejection (maxTokenAge)
    • Tests audience and issuer claim validation
  • E2E tests (ExternalJwksLoad.cy.ts):
    • Replay protection verification (same assertion rejected on second use)
    • Missing/expired/mismatched claim rejection
    • New Cypress task signCustomJwksAccessAssertion for crafting malformed assertions

Implementation Details

  • Extracted test helpers (generateTestKeyPair, baselineClaims, signClaims, expectVerificationToReject) for reusable claim validation testing
  • Fixed validation bugs: safeParse() now correctly checks .success property
  • Exported constants from @schemavaults/jwt index for use by auth-server and tests
  • Version bumps across affected packages (jwt, auth-server, auth-server-sdk, trpc-backend-init, e2e-auth-tests)

https://claude.ai/code/session_01WBBNRz39UahhGYVpSwRw2r

…erver:0.27.16, e2e-auth-tests:0.4.7 - harden JWKS access assertion validation

The auth server's JWKS access assertion verifier (used by both
/api/jwks/[audience] and the resource-server organization-role endpoint)
previously only checked the signature and sub claim. It now passes
audience (the auth server's app id), issuer (the requesting api_server_id),
maxTokenAge of 60s, and requiredClaims ["exp","iat","jti","aud","iss"] to
jose's jwtVerify, and treats every jti as single-use via a Redis SETNX
record held for twice the acceptance window, so captured assertions cannot
be replayed.

createJwksAccessProofToken now mints jti and iat claims and aligns exp with
the 60s server-side acceptance window; verifyJwksAccessProofToken applies
the same hardened verification options. Resource servers must upgrade
@schemavaults/jwt (directly or via @schemavaults/auth-server-sdk) before
the hardened auth server is deployed, since assertions minted without
jti/iat are now rejected.

Covered by new unit tests in @schemavaults/jwt (claim emission, missing
exp/iat/jti, expired, stale-but-unexpired, aud/iss mismatch) and E2E tests
asserting 401 for replayed, exp-less, expired, and aud/iss-mismatched
assertions against /api/jwks/[audience].

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_01WBBNRz39UahhGYVpSwRw2r
@jalexw jalexw merged commit ce08964 into main Jun 12, 2026
43 checks passed
@jalexw jalexw deleted the claude/bold-tesla-yhori5 branch June 12, 2026 23:21
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.

2 participants