Prompt and context
You own an OAuth 2.0 authorization server. Client authorization requests contain high-value scopes, resource indicators, and transaction context. The team wants the server to verify request origin and integrity, and sometimes hide those parameters. Design a JWT-Secured Authorization Request (JAR): Request Object format, signing and encryption, transport, lifetime, key rotation, and failure handling.
This question fits identity, payments, and backend roles. RFC 9101 puts authorization parameters in a JWT; JWS provides integrity and source authentication, while JWE can provide confidentiality. Distinguish JAR from PAR: JAR protects the request object, while PAR pushes it directly to the authorization server and returns a reference.
What the interviewer is testing
- Separation of responsibilities among the authorization endpoint, client, key directory, and token endpoint.
- Validation of issuer, audience, algorithm, time claims, and OAuth parameters.
- Handling of outer-parameter conflicts, replay, decompression or crypto resource abuse, and key rotation.
- Clear threat boundaries for JAR, PAR, PKCE, state, and nonce.
- Operationalization through observability, rollout, and rollback controls.
Clarifying questions
- Is the client public or confidential? Does the client sign the Request Object, or does a trusted backend sign it?
- Do we need integrity only, or must browsers and intermediaries be unable to read scopes, resources, and transaction fields?
- Can outer parameters override JWT claims? What are the policies for
requestandrequest_uriper client? - Is this OIDC login, payment authorization, or ordinary OAuth? Are nonce, step-up authentication, and non-repudiable audit required?
- Are keys statically registered, served through JWKS, or held in an HSM? What are rotation and revocation targets?
30-second answer
First establish who may issue the Request Object and which keys and algorithms are trusted. Validate iss, aud, client_id, redirect_uri, response_type, scopes, time claims, and a replay identifier. Use an allow-listed JWS for integrity and source authentication; add JWE when the request must be confidential. Treat protected JWT claims as authoritative and reject outer conflicts. Limit size and lifetime, track jti with a TTL, and fail closed for high-risk requests when verification is unavailable. JAR protects the object, PAR protects transport and reference lifetime, and PKCE binds the authorization-code redeemer.
Deep-dive answer
1. Establish the Request Object trust boundary
The client can send a JWT in request, or submit it through PAR and later reference it with request_uri. The authorization server uses client registration to select trusted issuers and algorithms; it must not fetch arbitrary keys just because a JWT header contains kid or jku.
Claims such as iss, aud, client_id, redirect_uri, response_type, scopes, and resources must match registration and the authorization context. Unsigned outer values cannot silently change signed values; conflicting or duplicated critical parameters are rejected.
2. Choose JWS, JWE, and algorithm policy
Use JWS when integrity and source authentication are required. Use JWE when the request contains fields that browsers or intermediaries should not read. Enforce an algorithm allow-list, reject none and algorithm switching, and cap header size, nesting, and total bytes.
Recipient keys for JWE come from trusted authorization-server registration. JWS verification keys come from the client registration or a trusted JWKS directory. Decryption, signature, and claim failures share one business error boundary so responses do not reveal key or request existence details.
3. Validate claims and time windows
Validate iss, aud, exp, and applicable nbf and iat claims. Keep exp short; allow only bounded clock skew. Use jti or an equivalent unique identifier for audit and replay detection, and reject a second use or require regeneration.
The server still performs exact redirect-URI matching and checks the client's allowed response types and scopes. A valid JAR signature does not mean that a user is authenticated or has consented; session, consent, assurance level, and risk decisions remain authorization-endpoint responsibilities.
4. Resolve outer parameters and request sources
Parse the request source before applying the specification's parameter precedence. If request and ordinary query parameters appear together, an unsigned outer value must not override a protected JWT claim. Mismatches, duplicates, or missing critical claims return invalid_request.
With PAR, the client sends the Request Object directly to the authorization server, which returns a short-lived request_uri. The browser then carries only the reference, reducing URL leakage and length limits. PAR does not replace JAR signing or encryption, and JAR does not manage reference lifetime by itself.
5. Resist replay, substitution, and resource abuse
Use client ID plus jti as a deduplication key in a TTL store; high-risk flows can require one-time consumption. Cap JWT size, nesting, decryption CPU, and JWKS refresh frequency to limit crypto and compression abuse.
Bind the Request Object to the client and redirect URI. Combined with PKCE, state, and OIDC nonce, this addresses authorization-code interception, session CSRF, and authentication-response substitution. Never log complete JWTs, transaction fields, or client assertions.
6. Design key directories and rotation
Give each client verification key and each authorization-server encryption key a version, purpose, and status. JWKS caches need explicit TTLs. During rotation publish the new key, switch issuers, retain the old key through the longest request or token lifetime, then revoke it.
If a kid is missing, allow one controlled directory refresh rather than unbounded external retries. Key revocation, directory outage, and algorithm mismatch need metrics and alerts; high-risk clients fail closed when verification cannot be completed.
7. Add observability, rollout, and rollback
Record a request-object hash, client, algorithm, verification result, hashed jti, and latency, never ciphertext or complete tokens. Track signature and decryption failures, claim conflicts, expiry, duplicate jti, JWKS refreshes, and authorization completion.
Enable JAR by client, compare completion rate, latency, and error distribution with the old flow, and pause rollout when key-directory or compatibility signals regress. Rollback should restore an explicitly approved policy; it should not turn unsigned outer parameters into a fallback for high-risk scopes.
Model answer
I would treat JAR as the integrity and confidentiality layer for an authorization request. The client or trusted backend creates a Request Object according to registration. The authorization server accepts only allow-listed algorithms and trusted keys, validates issuer, audience, client, redirect URI, response type, scopes, iat/exp, and jti, and rejects unsigned outer values that conflict with protected claims. Use JWS for integrity and source authentication and JWE for confidentiality.
Give the object a short lifetime and client binding, deduplicate jti with a TTL, and cap size, nesting, and decryption resources. Use versioned JWKS keys with overlap during rotation. JAR can compose with PAR: JAR protects the object, PAR hides browser parameters and manages a reference, while PKCE, state, and nonce retain their separate code and session bindings.
Roll out per client while observing verification failures, replay, JWKS refreshes, completion rate, and latency. Key-directory failures or unverifiable high-risk requests fail closed. Rollback restores a defined old policy and never treats unsigned outer parameters as equivalent protection.
Common mistakes
- Saying “JWT signatures are secure” without checking issuer, audience, algorithm, time, and redirect URI.
- Letting
kidorjkutrigger arbitrary network key retrieval, expanding trust and SSRF risk. - Allowing outer query values to override signed scopes or redirect URIs.
- Treating JAR, PAR, and PKCE as one feature instead of request, transport, and code protections.
- Omitting
jti, short TTLs, size caps, or crypto resource limits. - Deleting old keys immediately during rotation and breaking still-valid requests.
- Falling back unconditionally to ordinary authorization after verification fails.
Follow-up questions and answers
How do client and authorization-server signatures differ?
The client signature lets the server authenticate the request source and detect tampering. A server signature usually carries server attestation downstream. Their trust anchors, key purposes, and rotation owners differ.
Why is PKCE still needed when JAR is signed?
JAR protects request contents; PKCE protects the party redeeming the authorization code. An intercepted browser callback still needs the verifier.
Must every parameter be inside the JWT?
Put parameters that require integrity, source authentication, or confidentiality in the Request Object. Define precedence for permitted outer parameters and reject conflicts or missing critical values.
What if JWKS is temporarily unavailable?
Use short-lived caches and one controlled refresh. If no trusted key is available, fail closed for high-risk requests and alert. Never accept an unknown key or skip verification for availability.
How do you migrate clients that only support ordinary requests?
Enable JAR through client metadata, measure verification and completion, then require it for migrated clients. Explicitly configure the remaining scope, risk limits, and sunset date for the old flow.
How do you prove sensitive JWTs are absent from logs?
Apply field redaction at the gateway, authorization service, and error tracker. Retain only object and jti hashes, client, and result; sample logs and run regression checks that reject tokens, ciphertext, and transaction fields.