コンテンツにスキップ

JAR & JARM

このコンテンツはまだ日本語訳がありません。

JAR (JWT Secured Authorization Request, RFC 9101) and JARM (JWT Secured Authorization Response Mode) provide end-to-end integrity protection for the authorization flow:

  • JAR — Authorization parameters are sent as a signed JWT (Request Object), preventing parameter tampering
  • JARM — The authorization response is returned as a signed JWT, preventing response tampering

Together, they ensure that neither the authorization request nor the response can be modified by an attacker.

Instead of passing authorization parameters as query string values, the client creates a signed JWT containing all parameters and sends it in the request parameter:

Standard: /authorize?client_id=app&scope=openid&redirect_uri=...
With JAR: /authorize?client_id=app&request=eyJhbGciOiJSUzI1NiJ9...

The JWT is signed with the client’s private key, ensuring the authorization server can verify that the parameters have not been modified.

Create Request Objects using JARBuilder:

import { JARBuilder } from '@authrim/core';
const jarBuilder = new JARBuilder({
signJwt: async (header, claims) => {
// Sign the JWT with your private key
return signedJwtString;
},
keyId: 'my-key-id',
algorithm: 'RS256',
lifetime: 300, // seconds
});
ParameterTypeDefaultDescription
signJwt(header, claims) => Promise<string>RequiredJWT signing function
keyIdstringKey ID (kid) for the JWT header
algorithmstring'RS256'Signing algorithm
lifetimenumber300Request Object lifetime in seconds
const requestObject = await jarBuilder.buildRequestObject({
clientId: 'my-app',
issuer: 'https://auth.example.com',
redirectUri: 'https://myapp.com/callback',
scope: 'openid profile email',
state: generatedState,
nonce: generatedNonce,
codeChallenge: generatedChallenge,
codeChallengeMethod: 'S256',
});
// requestObject is a signed JWT string
ParameterTypeDescription
clientIdstringOAuth 2.0 client ID
issuerstringAuthorization server issuer URL (used as aud)
redirectUristringRedirect URI
scopestringRequested scopes
statestringCSRF protection state
noncestringReplay protection nonce
codeChallengestringPKCE code challenge
codeChallengeMethodstringChallenge method (always 'S256')
responseTypestringResponse type (default: 'code')
promptstringPrompt behavior
loginHintstringLogin hint
acrValuesstringAuthentication context
extraClaimsRecord<string, unknown>Additional JWT claims

The generated JWT includes:

ClaimDescription
issClient ID
audAuthorization server issuer URL
iatIssued at timestamp
expExpiration timestamp
jtiUnique identifier
nbfNot before timestamp
response_typeOAuth response type
client_idClient identifier
redirect_uriRedirect URI
scopeRequested scopes
stateCSRF protection value
nonceReplay protection value
code_challengePKCE code challenge
code_challenge_methodPKCE method

Build the signed request object with JARBuilder, then add it to the authorization request according to your application flow:

const requestObject = await jarBuilder.buildRequestObject({
clientId: 'my-app',
issuer: 'https://auth.example.com',
redirectUri: 'https://myapp.com/callback',
scope: 'openid profile',
state,
nonce,
codeChallenge,
codeChallengeMethod: 'S256',
});
const url = `${discovery.authorization_endpoint}?client_id=my-app&request=${encodeURIComponent(requestObject)}`;
import { isJarRequired } from '@authrim/core';
const discovery = await client.discover();
if (isJarRequired(discovery)) {
// Server requires signed request objects.
// Use JARBuilder to create the request object before redirecting.
}

The isJarRequired() function checks the require_signed_request_object field in the discovery document.


JARM (JWT Secured Authorization Response Mode)

Section titled “JARM (JWT Secured Authorization Response Mode)”

Instead of returning authorization parameters as query string values, the authorization server returns a signed JWT in the response parameter:

Standard: /callback?code=abc&state=xyz
With JARM: /callback?response=eyJhbGciOiJSUzI1NiJ9...

The JWT is signed by the authorization server, ensuring the client can verify that the response has not been tampered with.

Validate JARM responses using JARMValidator:

import { JARMValidator } from '@authrim/core';
const jarmValidator = new JARMValidator({
verifyJwt: async (jwt, issuer) => {
// Verify the JWT signature using the server's public key
return decodedClaims;
},
});
// Extract the response parameter from the callback URL
const responseJwt = JARMValidator.extractResponseFromCallback(callbackUrl);
if (responseJwt) {
const result = await jarmValidator.validateResponse(discovery, responseJwt, {
expectedState: storedState,
clientId: 'my-app',
clockSkewSeconds: 60,
});
console.log(result.code); // Authorization code
console.log(result.state); // Validated state
}
ParameterTypeDefaultDescription
expectedStatestringRequiredExpected state value for CSRF validation
clientIdstringRequiredClient ID (must match aud claim)
clockSkewSecondsnumber60Allowed clock skew in seconds
PropertyTypeDescription
codestringThe authorization code
statestringThe validated state parameter

The JWT returned by the server contains:

ClaimDescription
issAuthorization server issuer
audClient ID
expExpiration timestamp
codeAuthorization code (on success)
stateState parameter
errorError code (on failure)
error_descriptionError description (on failure)
import { JARBuilder, JARMValidator, isJarRequired } from '@authrim/core';
// Setup
const jarBuilder = new JARBuilder({
signJwt: mySignFunction,
algorithm: 'RS256',
});
const jarmValidator = new JARMValidator({
verifyJwt: myVerifyFunction,
});
// Step 1: Build authorization URL with JAR
const requestObject = await jarBuilder.buildRequestObject({
clientId: 'my-app',
issuer: 'https://auth.example.com',
redirectUri: 'https://myapp.com/callback',
scope: 'openid profile',
state: generatedState,
nonce: generatedNonce,
codeChallenge: generatedChallenge,
codeChallengeMethod: 'S256',
});
const authUrl = `${discovery.authorization_endpoint}?client_id=my-app&request=${requestObject}`;
// Step 2: After redirect, validate JARM response
const responseJwt = JARMValidator.extractResponseFromCallback(callbackUrl);
if (responseJwt) {
const { code, state } = await jarmValidator.validateResponse(
discovery,
responseJwt,
{ expectedState: storedState, clientId: 'my-app' },
);
// Step 3: Exchange code for tokens
const tokens = await exchangeCode(code);
}
Error CodeDescriptionRecovery
jar_signing_errorFailed to sign the Request ObjectCheck signing key and configuration
jar_requiredServer requires JAR but it wasn’t usedSet useJar: true
jarm_validation_errorJARM response validation failedCheck server configuration
jarm_signature_invalidJARM JWT signature verification failedVerify server keys