RFC 9101 — JWT-Secured Authorization Request (JAR)
Spec: datatracker.ietf.org/doc/html/rfc9101Status: Full
RFC 9101 (formerly OpenID Connect Core §6) lets a client pack the authorization request into a signed JWT — the Request Object — instead of sending parameters as plain query strings. This protects the request from tampering in transit and lets a client be authenticated at /authorize rather than only at /token.
Two delivery modes are supported:
request=<JWT>— the JWT is sent inline as a parameter (§6.1).request_uri=<URL>— the JWT is hosted at a URL the OP fetches (§6.2).
WARNING
Earlier AuthHero versions decoded the request JWT without verifying its signature. Any caller could forge claims and have them merged into the authorization request. This page describes the post-fix behavior; that hole is now closed.
How verification works
request JWTs are signature-verified against the client's keys before any claim is applied to the authorization request. request_uri URLs are first fetched (with the SSRF guard described below) and the body runs through the same verifier.
Supported algorithms
| Family | Algorithms | Verified against |
|---|---|---|
| Asymmetric | RS256, RS384, RS512, ES256, ES384, ES512 | Client's registration_metadata.jwks (inline) or client_metadata.jwks_uri (fetched) |
| Symmetric | HS256, HS384, HS512 | Client's stored client_secret |
alg=none is rejected.
Claim validation
The verifier enforces:
client_id— required, and must equal the outerclient_idparameter (§5).iss— must equalclient_idwhen present.aud— must match the OP issuer when present.exp— required, validated with 30 s leeway.nbf— validated with 30 s leeway when present.
Sending both request and request_uri is rejected with HTTP 400 per the spec.
Parameter handling
When a Request Object is supplied, AuthHero uses only the parameters inside it (§5). The outer query string is read for three things: request or request_uri to find the object, and client_id to find the keys that verify it. Everything else in the query is ignored, including:
- Duplicates. If
state,nonce,scope,redirect_urior any other parameter appears in both places with different values, the signed value wins. The request is not rejected. - Gaps. A parameter that is missing from the Request Object cannot be supplied through the query string. A missing
response_typefails the request with an error. The error is returned to the signedredirect_uri. - AuthHero extensions.
connection,screen_hint,login_ticket,realm,vendor_id,auth0Clientand the other AuthHero-specific parameters follow the same rule. To use one with a Request Object, sign it into the object. - Stored login sessions. A plain
/authorizerequest whosestatematches an unfinished login session is filled in from that session's stored parameters. Signed requests never are.
Requests without a Request Object work exactly as before.
A Request Object without redirect_uri behaves like a plain /authorize request without one: the login flow falls back to the client's registered callback. Profiles such as FAPI require the request to fail instead. That per-client enforcement is not implemented yet (see below).
SSRF guard for request_uri
request_uri is a server-side fetch that the client controls, so it's exposed to SSRF. AuthHero applies these defaults:
https://only —http://URLs are rejected.- No
localhost/127.0.0.0/8/ link-local / private IPv4 ranges. - No private IPv6 ranges (loopback, link-local, ULA).
- 5-second timeout on the fetch.
- 64 KiB body cap — larger bodies are truncated and rejected.
For local development and tests, set ALLOW_PRIVATE_OUTBOUND_FETCH: true on the env binding to relax this guard (allows http://, localhost, and private IPs).
Discovery
/.well-known/openid-configuration advertises:
{
"request_parameter_supported": true,
"request_uri_parameter_supported": true,
"request_object_signing_alg_values_supported": [
"RS256", "RS384", "RS512",
"ES256", "ES384", "ES512",
"HS256", "HS384", "HS512"
]
}Partial / not yet implemented
request_uripre-registration — the spec allows an OP to require clients to pre-register theirrequest_urivalues; AuthHero currently accepts any URL that passes the SSRF guard.- Encrypted request objects (JWE) — only signed JWTs are accepted.
require_signed_request_objectper-client flag — there's no client-level setting yet to require request objects on every authorize call.- FAPI-profile checks —
redirect_uri,nonce,nbfand a boundedexplifetime are not required inside the Request Object.
Test coverage
The parameter-handling rules are covered by regression tests in packages/authhero/test/routes/auth-api/request-object.spec.ts. They cover both inline request and fetched request_uri objects: query-only parameters are ignored, signed values win over differing duplicates, and gaps cannot be filled from the query. They also check that a missing or mismatched client_id is rejected and that plain query requests are unchanged.
The conformance runner exercises these OIDC plan modules:
oidcc-request-uri-unsignedoidcc-unsigned-request-object-supported-correctly-or-rejected-as-unsupportedoidcc-ensure-request-object-with-redirect-uri
The two unsigned-object modules pass when an OP correctly rejects alg=none. Passing them does not show that JAR is implemented.
The upstream suite checks the signed-only rule itself in FAPI plans. Those plans are not wired into the runner yet:
| Upstream module | What it checks |
|---|---|
fapi1-advanced-final-state-only-outside-request-object-not-used | a query-only state is not echoed back |
fapi1-advanced-final-ensure-different-nonce-inside-and-outside-request-object | the signed nonce wins over a differing query value |
fapi2-security-profile-final-ensure-different-state-inside-and-outside-request-object | the signed state wins over a differing query value |
fapi1-advanced-final-ensure-request-object-without-scope-fails | a query scope cannot fill a gap in the Request Object |
fapi1-advanced-final-ensure-request-object-without-redirect-uri-fails | FAPI profile: redirect_uri is required in the Request Object |
fapi1-advanced-final-ensure-request-object-without-nonce-fails | FAPI profile: nonce is required in the Request Object |
The first four match the behavior described above. The last two also depend on FAPI-profile requirements that AuthHero does not enforce yet.
Related AuthHero documentation
- OpenID Connect Core — §6 request objects
- RFC 7523 — Client Assertion — uses the same JWT signature verifier on the token endpoint
- Conformance Testing — see Test coverage for which modules run and which don't