JWT-secured authorisation response (JARM)

The FAPI working group, chartered to develop a high-security OAuth 2.0 profile for financial applications (as in Open Banking), has come up with a specification for signed and optionally encrypted authorisation responses, called JARM.

The response parameters, such as the authorisation code and the state in the code flow, are relayed in a JSON Web Token (JWT). This enhances the response with the following security properties:

  • Authentication of the issuing OpenID Provider / Authorisation Server;
  • Restricting the response audience to the client ID;
  • Protection against replay, credential leaks and mix-up attacks.

JARM stands for JWT-Secured Authorization Response Mode.

How does JARM work?

The JARM spec defines a new response mode (response_mode) where all parameters are packaged into a JWT that is signed by the OpenID Provider / Authorisation Server. This naturally also includes the parameters of error responses and any present and future OAuth 2.0 and OpenID Connect extensions.

Example of a regular authorisation response, as redirection URI:

https://client.example.com/cb?
 code=PyyFaux2o7Q0YfXBU32jhw.5FXSQpvr8akv9CeRDSd0QA
 &state=S8NJ7uqk5fY4EjNvP_G_FtyJu6pUsvH9jsYni9dMAJw

In JARM, the response parameters are packaged into a JWT, which must includes the claims issuer (iss), audience (aud) and expiration time (exp) to ensure the JWT has a clearly identified origin and destination (the client_id), and a limited lifespan.

Example JWT claims for the above response:

{
  "iss"   : "https://accounts.example.com",
  "aud"   : "s6BhdRkqt3",
  "exp"   : 1311281970,
  "code"  : "PyyFaux2o7Q0YfXBU32jhw.5FXSQpvr8akv9CeRDSd0QA",
  "state" : "S8NJ7uqk5fY4EjNvP_G_FtyJu6pUsvH9jsYni9dMAJw"
}

The JWT is digitally signed or HMAC-protected, and can be additionally encrypted for confidentiality (resulting in a nested JWT).

Example final authorisation response where the parameters have been JWT-secured, the JWT is passed in a "response" query or fragment parameter, or form-posted, depending on the requested / implied response mode:

https://client.example.com/cb?
 response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

How does an Authorisation Server / OpenID Provider advertise support for JARM?

An OAuth 2.0 Authorisation Server or OpenID Provider shows JARM support by publishing the supported JARM response modes and JOSE algorithms in its metadata:

  • response_modes_supported -- Must include query.jwt, fragment.jwt, form_post.jwt and / or jwt (shortcut response mode for the specific ones).
  • authorization_signing_alg_values_supported -- The supported JWS algorithms for JARM. The none algorithm, i.e. a plain JWT, is forbidden. If the client doesn't have a JWS algorithm registered for JARM and requests a JWT-secured response_mode the default algorithm is RS256.
  • authorization_encryption_alg_values_supported -- The supported JWE algorithms (if encryption is supported).
  • authorization_encryption_enc_values_supported -- The supported JWE methods (if encryption is supported).

You can find out if an Authorisation Server supports JARM by retrieving its metadata. Here how you can do that using this SDK:

import java.util.*;
import com.nimbusds.oauth2.sdk.as.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.oauth2.sdk.jarm.*;

// The Authorisation Server issuer URI
Issuer as = new Issuer("https://c2id.com");

// Fetch the metadata from https://c2id.com/.well-known/oauth-authorization-server
AuthorizationServerMetadata asMetadata = AuthorizationServerMetadata.resolve(issuer);

// Display JARM support
if (JARMUtils.supportsJARM(asMetadata)) {

    System.out.println("JARM supported");

    for (ResponseMode rm: asMetadata.getResponseModes()) {
        if (JARMUtils.RESPONSE_MODES.contains(rm)) {
            System.out.println("Supported JARM response mode: " + rm);
        }
    }

    List<JWSAlgorithm> jarmJWSAlgs = asMetadata.getAuthorizationJWSAlgs();
    System.out.println("Supported JARM JWS algorithms: " + jarmJWSAlgs);

    List<JWEAlgorithm> jarmJWEAlgs = asMetadata.getAuthorizationJWEAlgs();
    System.out.println("Supported JARM JWE algorithms: " + jarmJWEAlgs);

    List<EncryptionMethod> jarmJWEEncs = asMetadata.getAuthorizationJWEEncs();
    System.out.println("Supported JARM JWE methods: " + jarmJWEEncs);
}

To resolve the metadata for an OpenID Provider use the extending OIDCProviderMetadata class instead.

How to register an OAuth 2.0 client for JARM?

Simply set the authorization_signed_response_alg client metadata to the desired JWS algorithm (which the OAuth 2.0 server must support) to have the responses signed.

import com.nimbusds.oauth2.sdk.client.*;
import com.nimbusds.jose.*;

ClientMetadata clientMetadata = new ClientMetadata();
clientMetadata.setAuthorizationJWSAlg(JWSAlgorithm.RS256);
// ... proceed with setting metadata parameters, such as redirect_uris

The method is identical if you're registering an OpenID relying party, just use the extending OIDCClientMetadata class instead.

How to make an OAuth 2.0 authorisation request for JARM?

Set the optional response_mode parameter to the specific JARM mode, e.g. query.jwt or the jwt shortcut. If the Authorisation Server has the client registered for JARM an explicit request for JARM may not be needed.

import java.net.*;
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.id.*;

ResponseType rt = new ResponseType("code");
ResponseMode rm = ResponseMode.resolveJARM(rt);
ClientID clientID = new ClientID("123");
State state = new State();

URI redirectionURI = new AuthorizationRequest.Builder(rt, clientID)
    .state(state)
    .responseMode(rm)
    .build()
    .toURI();

For an OpenID authentication request use the AuthenticationRequest class instead.

How to parse and validate a JARM response?

Create a JARM validator for the expected Authorisation Server issuer URI, client ID and JWS algorithm, and provide it with a source for the signing keys.

You can easily create a JARM validator from stored Authorisation Server and the client's registration:

import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.as.*;
import com.nimbusds.oauth2.sdk.client.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.oauth2.sdk.jarm.*;

// The AS metadata
AuthorizationServerMetadata asMetadata = ...;

// The client registration
ClientInformation clientInfo = ...;

// Create JARM validator, will automatically resolve the Authorisation
// Server JWK set and cache it locally
JARMValidator jarmValidator = JARMValidator.create(asMetadata, clientInfo);

// Get the callback URI (framework specific)
URI callback = ...;

// Parse the callback and validate the response JWT
AuthorizationResponse response = AuthorizationResponse.parse(callback, jarmValidator);

if (response.indicatesSuccess()) {
    AuthorizationSuccessResponse successResponse = response.toSuccessResponse();
    System.out.println("Code: " + successResponse.getAuthorizationCode());
    System.out.println("State: " + successResponse.getState());
} else {
    AuthorizationErrorResponse errorResponse = response.toErrorResponse();
    System.out.println("Error code: " + errorResponse.getErrorObject().getCode());
    System.out.println("Error description: " + errorResponse.getErrorObject().getDescription());
}