JWT-Secured Authorization Request (JAR)
RFC 9101 JWT-Secured Authorization Request — the authorization- server side of the request object, the counterpart to the JARM response handling in b.auth.oauth. A plain OAuth authorization request passes its parameters as URL query string, where they can be tampered with in the browser or leaked into proxy / referer logs. JAR packs the parameters into a JWT signed by the client (the "request object") so the authorization server can verify they arrived exactly as the client sent them.
b.auth.jar.parse(jar, opts) verifies an incoming request object (the authorization-server side): the signature is checked through b.auth.jwt.verifyExternal (mandatory algorithms allowlist — no alg: "none", no HMAC-vs-RSA confusion, no JWE-on-a-JWS-verifier), iss is pinned to the expected clientId, aud to this server's issuer identifier, the request object's client_id claim must match the client, and the authorization parameters are returned with the JWT envelope claims stripped.
b.auth.jar.build(params, opts) mints a request object (the client side): the authorization-request parameters become claims of a JWT signed with the client's classical key via b.auth.jws.sign (RS/PS/ES/EdDSA — the interop algs an authorization server accepts), typed oauth-authz-req+jwt, with iss/aud pinned and a short FAPI-2 exp. build and parse round-trip. The framework's own tokens stay PQC-signed (b.auth.jwt); JAR signs classically only because no standard authorization server verifies a PQC request object today.
Anti-nesting (RFC 9101 §6.3): a request object may not itself carry a request or request_uri parameter — parse refuses it, closing the recursion / confused-deputy vector.
The signature verification — the security-critical step — is delegated to verifyExternal, which already enforces the alg allowlist and refuses the alg-confusion / JWE-bypass shapes against a JWKS public-key trust source. JAR adds the request-object-specific bindings on top.
The request object is signed with a classical JWS algorithm (RS/PS/ES/EdDSA) because no standard authorization server verifies a PQC-signed request object today; the framework's own JWT signer (b.auth.jwt.sign) stays PQC-only (ML-DSA / SLH-DSA) for the tokens blamejs itself issues. build composes b.auth.jws.sign for the classical signature — the client-side emission this module previously left to the operator's own JOSE tooling now ships in-framework.
b.auth.jar.parse(jar, opts) #
{
{
clientId: string, // required — expected client (iss + client_id pin)
audience: string, // required — this server's issuer identifier (aud pin)
algorithms: string[], // required — accepted signature algorithms (allowlist)
jwks?: object, // one of jwks / jwksUri / keyResolver (the client's key)
jwksUri?: string,
keyResolver?: function,
clockSkewMs?: number,
}
}
Verify an RFC 9101 request object and return its authorization parameters. The signature is checked via b.auth.jwt.verifyExternal (mandatory algorithms allowlist), iss is pinned to opts.clientId, aud to opts.audience, and the request object's client_id claim must equal opts.clientId. A request object carrying a nested request / request_uri is refused (RFC 9101 §6.3). Returns { params, claims } where params is the authorization parameters with the JWT envelope claims removed.
var out = await b.auth.jar.parse(jar, {
clientId: "s6BhdRkqt3",
audience: "https://as.example.com",
algorithms: ["ES256"],
jwks: clientJwks,
});
// → { params: { response_type: "code", redirect_uri: "...", ... }, claims: {...} }
b.auth.jar.build(params, opts) #
{
{
clientId: string, // required — → iss + must equal params.client_id
audience: string, // required — AS issuer identifier → aud
key: KeyObject|PEM|JWK, // required — client's classical signing key
alg?: string, // JWS alg override (default inferred from the key)
kid?: string, // protected-header kid (JWKS selection at the AS)
expiresInMs?: number, // exp = iat + this (default: 5m; positive int)
}
}
Mint an RFC 9101 request object — the client side of JWT-Secured Authorization Requests. The authorization-request parameters in params become claims of a JWT signed with the client's classical key, ready to send as the request parameter (or pushed through PAR). The inverse of b.auth.jar.parse; the two round-trip.
The protected header carries typ: "oauth-authz-req+jwt" (RFC 9101 §10.8 — explicit typing closes the cross-JWT-confusion vector where a token minted for another purpose is replayed as a request object). iss is set to opts.clientId and aud to opts.audience — the authorization server's issuer identifier (RFC 9101 §5; the FAPI 2.0 message-signing profile requires both). response_type and client_id are REQUIRED claims (RFC 9101 §4); the builder refuses if either is absent from params. A request object MUST NOT nest request / request_uri (RFC 9101 §4) — supplying either in params is refused at build, the mirror of parse's anti-nesting check.
exp defaults to 5 minutes (FAPI-2 wants a short signing window; tune via opts.expiresInMs); nbf is set to iat and a random jti is minted so the AS can single-use the object. The signing alg is derived from opts.key via b.auth.jws.sign (RS/PS/ES/EdDSA); alg: "none" is impossible — the signer refuses it. This is the classical-interop path: the framework's own tokens stay PQC-signed.
var ro = b.auth.jar.build(
{ response_type: "code", client_id: "s6BhdRkqt3",
redirect_uri: "https://app/cb", scope: "openid", state: "xyz" },
{ clientId: "s6BhdRkqt3", audience: "https://as.example.com", key: clientKey, kid: "c1" });
// → "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QiLCJraWQiOiJjMSJ9..."
Last updated 2026-08-08T16:39:49.652Z by seeder.