JWT secured authorisation response (JARM)

The FAPI working group, which has taken the important job of developing a high-security OAuth 2.0 profile for financial applications (as in Open Banking), has come up with a new specification, called JARM, for signed and optionally encrypted authorisation responses. The response parameters, such as authorisation code and state for the code flow, are relayed as a JSON Web Token (JWT) payload, enhancing the response with the security properties of authenticating the issuer, restricting the audience to the client ID as well as protecting from 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 where the parameters are packaged into a JWT that is signed by the Authorisation Server. That also covers error responses and OpenID authentication responses.

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 includes the additional mandatory claims issuer (iss), audience (aud) and expiration time (exp) claims 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 signed or HMAC-protected, and can be additionally encrypted for confidentiality.

Example final authorisation response where the parameters have been JWT-secured, the JWT is passed in a "response" query or fragment parameter, of 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 for JARM in its metadata:

  • response_modes_supported -- Must include query.jwt, fragment.jwt, form_post.jwt and / or jwt (shortcut response mode for the more specific ones).
  • authorization_signing_alg_values_supported -- The supported JWS algorithms for JARM. The none algorithm, i.e. a plain JWT is forbidden.
  • 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.*;

// 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 (asMetadata.getResponseModes().contains(ResponseMode.JWT)) {
    System.out.println("JARM supported");
}
List<JWSAlgorithm> jarmJWSAlgs = asMetadata.getAuthorizationJWSAlgs();
System.out.println("Supported JARM JWS algorithms: " + jarmJWSAlgs);

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.*;

URI redirectionURI = new AuthorizationRequest.Builder(
    new ResponseType("code"),
    new ClientID("123"))
    .state(new State("xyz"))
    .responseMode(ResponseMode.QUERY_JWT)
    .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());
}

Availability

Support for JARM was added in version 6.3 of this SDK.