JWT Secured Authorization Response Mode (JARM)

JWT Secured Authorization Response Mode (JARM)

On this page

The OAuth and OIDC Request Objects and Pushed Authorization Requests (PAR) define mechanisms for securing the authorization request. JWT Secured Authorization Response Mode (JARM), on the other hand, addresses the authorization response. The specification defines new values for the response_mode so that the client can request to receive the authorization response in JWT format. The returned JWT includes all the authorization or error response parameters plus some additional claims for further protection.

Specification

JARM defines new response modes for authorization responses. This means that it works with any response type. Via specifying the response mode, the client tells the authorization server how to craft the authorization response. Using JARM, the authorization server can transfer a JWT in the query or fragment part of the redirect URL, or via an auto-submitted form (body).

The new values defined by JARM for the response_mode are therefore:

  • query.jwt
  • fragment.jwt
  • form_post.jwt
  • jwt

The response mode jwt has a special meaning. It is a shortcut for the default value. The default JWT response mode for the code flow (response_type=code) is defined as query.jwt whereas for the implicit flow and for the hybrid flow the default is fragment.jwt. For simplicity, the client can just specify response_mode=jwt and the authorization server will return the response using the default mode based on the response_type.

The payload of the JWT depends on the flow, i.e. the value of the response_type parameter in the request and corresponding authorization response. For example, in a code flow (response_type=code) the server is supposed to return the code and state parameters. In JARM those parameters are part of the JWT payload:

{
  "exp": 1657190372,
  "iss": "https//login.example.com/oauth/v2/oauth-anonymous",
  "aud": "web-client",
  "iat": 1657190352,
  "purpose": "authz_response",
  "code": "M5JfXfDZ0UXHLEdTxEpN7EdPBLzCjyV9",
  "state": "1599045135410-jFe"
}

The JWT also contains at least the issuer (iss), audience (aud) and expiration time (exp). The client must decrypt the JWT if encrypted, and validate it. First, the client must verify the issuer as well as the audience against expected values. Then the client validates the expiration time and signature. Only if the JWT passes all checks, the client may make use of the authorization response parameters and proceed.

This article includes some examples for different flows and response modes. For the details of authorization request and corresponding response parameters refer to the documentation of OAuth Flows.

Why JARM?

Implementing the JWT Secured Authorization Response Mode may simply be a question of compliance, i.e. when having to comply with FAPI 1.0 - Advanced Profile. JARM mitigates risks where an attacker can manipulate or read the authorization response. Because the authorization response is returned as a JWT, its parameters are either signed, or signed and encrypted, which means that a man in the browser (or man in the middle) cannot easily change parameters in the response unnoticed or, when applying encryption, cannot even read the data. In other words JARM provides integrity and privacy protection.

JARM also prevents a so-called Mix-Up attack. This attack can occur when a client integrates with several authorization providers, a scenario common in Open Banking where each financial service provider maintains its own authorization server. The attack works, because the client cannot - without any mitigation - distinguish the authorization response from an honest authorization server from one sent by the attacker's authorization server. Consequently, the client easily happens to mix the request to one authorization server with the response from another because it cannot verify that the response comes from the authorization server that it started the flow at. When encoding the authorization response in a JWT, the JWT contains an issuer claim, thus the client can verify that the response it receives comes from the correct authorization server.

Examples

Code Flow and query.jwt

The client starts the code flow with an authorization request sending response_type=code and response_mode=jwt together with the other parameters of the authorization request. In the following example, the client makes also use of the state parameter and PKCE (line breaks for display purposes):

GET https://login.example.com/oauth/v2/oauth-authorize?
&client_id=web-client
&response_type=code
&redirect_uri=https://www.example.com/callback/code
&state=1599045135410-jFe
&scope=read
&code_challenge=rerbvXfTDYNECzwayM8-SLCWU1FDzBnqMCv1RB5AudU
&code_challenge_method=S256
&response_mode=jwt

The default JWT response mode for the code flow is query.jwt. Thus, the authorization server returns the response as a JWT in the query parameter of the HTTP message (line breaks for display purposes):

HTTP/2 302 Found
Location: https://www.example.com/callback/code?response=
eyJraWQiOiI4ODEyMDM5NTUiLCJ4NXQiOiJWNE14UU5ZX0o1Tjl5eXdHOEkyajJh
Q2JnaFEiLCJhbGciOiJSUzI1NiJ9.
eyJleHAiOjE2NTcxOTAzNzIsImlzcyI6Imh0dHBzLy9sb2dpbi5leGFtcGxlLmNv
bS9vYXV0aC92Mi9vYXV0aC1hbm9ueW1vdXMiLCJhdWQiOiJ3ZWItY2xpZW50Iiwi
aWF0IjoxNjU3MTkwMzUyLCJwdXJwb3NlIjoiYXV0aHpfcmVzcG9uc2UiLCJjb2Rl
IjoiTTVKZlhmRFowVVhITEVkVHhFcE43RWRQQkx6Q2p5VjkiLCJzdGF0ZSI6IjE1
OTkwNDUxMzU0MTAtakZlIn0.
QoFtbRF69W8-rXvjz3UwSb3LjK38ioN5qkFyY1qZE7y9P08Oh39c2x3evOl19Q5I
8IrfRI94xdRN4kdTmg046ggcczDb4VkPKSeB2YcCfZzUHulS6XSKf-eJsszhsyeO
Ip8y-wpu7lnm4KZXfq3BmIZcl49q2Ac84VXyXX6-LOQ04Twm4nZYe6yenzUNE4J5
hTcvzrgjShQPs8NqyHsaimNHcqilolj8PvQLJC-TNSasZGI0OjmUxa8msJD-kq2f
NJFoVpEIrmZthXHLUUu3N67MBEEih-tskX8DwDpPxvW8iSi3PkkOc1acH_hsXSll
0dvJ_p38-Dyz7rz883C6mA

The response parameter represents the JWT with the code and state from the authorization response as well as the issuer, audience and expiration time along with other claims in the payload.

{
  "exp": 1657190372,
  "iss": "https//login.example.com/oauth/v2/oauth-anonymous",
  "aud": "web-client",
  "iat": 1657190352,
  "purpose": "authz_response",
  "code": "M5JfXfDZ0UXHLEdTxEpN7EdPBLzCjyV9",
  "state": "1599045135410-jFe"
}

The client must validate the JWT before it checks the state and exchanges the code for a token.

Implicit Flow and fragment.jwt

The client starts the implicit flow with an authorization request sending response_type=token and response_mode=jwt together with the other parameters of the authorization request.

GET https://login.example.com/oauth/v2/oauth-authorize?
client_id=web-client
&redirect_uri=https://www.example.com/callback/implicit
&response_type=token
&state=1599045172021-mww
&scope=read
&response_mode=jwt

The default JWT response mode for the implicit flow is fragment.jwt. Thus, the server returns the response as a JWT in a URL fragment (line breaks for display purposes).

HTTP/2 302 Found
Location: https://www.example.com/callback/implicit#response=
eyJraWQiOiI4ODEyMDM5NTUiLCJ4NXQiOiJWNE14UU5ZX0o1Tjl5eXdHOEkyajJh
Q2JnaFEiLCJhbGciOiJSUzI1NiJ9.
eyJleHAiOjE2NTcyMTg4MDgsImlzcyI6Imh0dHBzLy9sb2dpbi5leGFtcGxlLmNv
bS9vYXV0aC92Mi9vYXV0aC1hbm9ueW1vdXMiLCJhdWQiOiJ3ZWItY2xpZW50Iiwi
aWF0IjoxNjU3MjE4Nzg4LCJwdXJwb3NlIjoiYXV0aHpfcmVzcG9uc2UiLCJhY2Nl
c3NfdG9rZW4iOiJfMFhCUFdRUV9lYzM4Mzc5OC1iMTM2LTRkZTEtYjE5MC01OTBh
ZjBkZDFmZmYiLCJzdGF0ZSI6IjE1OTkwNDEyMzQ4NTkiLCJ0b2tlbl90eXBlIjoi
YmVhcmVyIiwiZXhwaXJlc19pbiI6IjMwMCIsInNjb3BlIjoicmVhZCJ9.
HM8efWTQDenRN-Q8xpfVaHfhZo6wE86HTX6EnktnUBRb4c9qHPs2HgbAZwThTRb-
Men3WXrd929Obqv6JY9_n1Vs65U5lMcb5G2W1KjIaPquBG7MxBG2q_B3hq6uhzk5
mOlqPxIQwQ8VIMiaIGY892_TA8cZlFIZzbvPViZj9VBy2JaZHlEhLdsTXSF7_RTh
MNdput4pz1D2BTuQgk0MBJvAyJWU9H9wMNlQq89m1_ztfluLXfMj5bvYqpCqEueO
vWHlyENVp0SUdOUpNGsFqOwphXPH1z5EVR7_0On2d3QEMWkunytOEdl8bpXi-hE9
y-hVCBBvPXi9_fv8qUji7A

A URL fragment and consequently the JWT response is only accessible from the browser, e.g. JavaScript. The JWT contains the access_token, token_type, expires_in, scope and state claims that are the authorization response parameters of the implicit flow.

{
  "exp": 1657218808,
  "iss": "https//login.example.com/oauth/v2/oauth-anonymous",
  "aud": "web-client",
  "iat": 1657218788,
  "purpose": "authz_response",
  "access_token": "_0XBPWQQ_ec383798-b136-4de1-b190-590af0dd1fff",
  "state": "1599041234859",
  "token_type": "bearer",
  "expires_in": "300",
  "scope": "read"
}

The client must validate the JWT before it is allowed to use the access token.

Example - Hybrid Flow and post_form.jwt

It is possible to send the response in the body of a request using response_mode=post_form.jwt. Study the following example of a hybrid flow that uses response_type=code id_token and response_mode=post_form.jwt. The example makes use of the state parameter and PKCE (line breaks for display purposes).

GET https://login.example.com/oauth/v2/oauth-authorize?
client_id=web-client
&response_type=code%20id_token
&redirect_uri=https://www.example.com/callback/hybrid
&state=1599045202487-sVG
&scope=openid%20%20read
&code_challenge=y2upJc5ieYeoQaU0c25iG87qxEXXC7h_0LvIYgQ6ufk
&code_challenge_method=S256
&nonce=1657220314981-cBR
&response_mode=post_form.jwt

The response is an HTTP form with the JWT that the browser automatically submits to the redirect_uri (line breaks and abbreviation for display purposes):

HTTP/2 200 OK
Content-Type: text/html;charset=UTF-8
Cache-Control: no-cache, no-store
Pragma: no-cache

<html>
  <head><title>Submit Authorization Response with Form</title></head>
  <body onload="javascript:document.forms[0].submit()">
    <form method="post" action="https://www.example.com/callback/hybrid">
      <input type="hidden" name="response"
        value="eyJraWQiOiI4ODEyMDM5NTUiLCJ4NXQiOiJWNE14UU5ZX0o1Tjl5eXdHOEkyajJhQ2JnaFEiLCJhbGciOiJSUzI1NiJ9.
        eyJleHAiOjE2NTcyNjMyNDMsImlzcyI6Imh0dHBzLy9sb2dpbi5leGFtcGxlLmNvbS9vYXV0aC92Mi9vYXV0aC1hbm9ueW1vdX
        ...
        Z5TUtiR2h2VCtaSm9VQ2M1L2FETkM0NmZhSXJmWVx1MDAzZC5WMnFObjRsRmRZNTgifQ.
        uJ86oH6IHLTJkfkPygNT35YscyFpQ5Nz_MS-l92oYoL15jTXBaus1jPgFExYzpb104Fsu4-Z1WZrQkGGh8xjDmupVzXEEJUJAF
        h4AI4jVWriptXwHEdN8icMYdEUrbv8cJWe5I-A5MdH5oNr1KX2fHPrrDMqCF1br8Gg48w9ze_kw5n7BakPUzhBrRkMTcH19yIx
        bDTCXlZEMhxEbPaY3ESxC0_xyNMImSVBeUX_TJy_IPYH1ozodVz8jDYqrplikCOEf1joX1eRDrZZdEDTdaHus39I0PK7Wn_WCD
        cC-v2e6cC_zSvq6pUZgTQ1dAGBHs4vHdjHCtfQafEkmezYQA"/>
    </form>
  </body>
</html>

The client requested an ID token together with the code thus the JWT contains claims for both (abbreviation for display purposes):

{
  "exp": 1657263243,
  "iss": "https//login.example.com/oauth/v2/oauth-anonymous",
  "aud": "web-client",
  "iat": 1657263223,
  "purpose": "authz_response",
  "id_token": "eyJraWQiOiI4ODEyMDM5NTUiLCJ4NXQiO...acz3ofJIia2IFwR5Re6sdOHuiA",
  "code": "xD04zGiv7S5caUN9iaWLEeeLR3EeDRyw",
  "state": "1599045202487-sVG",
  "session_state": "tRvKMTUQFyNNPFyMKbGhvT+ZJoUCc5/aDNC46faIrfY=.V2qNn4lFdY58"
}

The client must validate the JWT before it uses any of the claims for further processing.

Conclusion

JWT Secured Authorization Response Mode is enforced by the client. It allows the client to request an authorization response in JWT form. JWTs provide integrity protection via signatures and privacy protection via encryption - requirements common in financial-grade APIs.

JWTs are a very popular format for access tokens and ID tokens are always encoded as JWTs, thus clients are familiar with the format and commonly already have JWT processing capabilities. Returning the authorization response in JWT form is an easy and simple mechanism to protect the messages from the server.