Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

OAUTH 2.0 · LAB

Ask for a signed response in each JARM response mode

Confirm JARM support from metadata, request query.jwt and form_post.jwt responses for lab-printer, and read each JWT's header and claims, including the algorithm the client agreed.

PlannedUses your lab tenant

The lesson

Builds on: Protecting an authorization response.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Planned. The core of this lab waits on platform features that are not built yet. The planned walkthrough shows exactly how it will run; Do today is a real exercise you can do now.

Request console

Requests in this lab can be sent from this page to your tenant: open one and choose Send. Fill in the values below first. They stay in this page's memory and are gone when you leave; secrets are never stored or sent anywhere except the request you send.

Setup

  1. Use lab-printer, the shell variables and the two listeners from Read a plain authorization response, then plan a signed one: btl-lab callback for query responses and nc -l 8765 for form posts.

  2. Planned (G13): allow the JWT response modes as in that lab, but leave lab-printer's authorization response signing algorithm empty at first.

Planned walkthrough

  1. Confirm support before relying on it.

GET$ISSUER/.well-known/oauth-authorization-server Open in console
GET $ISSUER/.well-known/oauth-authorization-server

Expect the *.jwt modes in response_modes_supported and authorization_signing_alg_values_supported listing ES256 and RS256.

Why it matters: "Asking for a signed response". The client checks the server's metadata and agrees an algorithm before it changes its requests.

  1. With no algorithm set on the client, run a code flow with &response_mode=jwt and decode the response with btl-lab decode "$RESPONSE". The header says "alg":"RS256". Now set lab-printer's algorithm to ES256 and repeat: the header says "alg":"ES256" with the kid of the tenant's ES256 key.

Why it matters: the lesson's warning about JARM's RS256 default is a real per-client setting, and an unset value can break interoperability for reasons unrelated to security.

  1. Note where the jwt response arrived: as ?response=... in the query string, with no code, state or iss beside it.

Why it matters: "Choosing a response mode". For the code flow, jwt means query.jwt.

  1. Repeat with &response_mode=form_post.jwt while nc -l 8765 listens. The POST body holds response=eyJ... and nothing else.

Why it matters: the JWT stays out of the address bar, history and the Referer header. Your pending-attempt cookie must survive a cross-site POST, or the client will not find it at its callback.

  1. Read the claims of both responses: iss, aud (your client ID), exp, code, state. Compare aud with the aud of the request object claims from the JAR labs, which named the issuer.

Why it matters: "Reading the response". Each signed message names the party that should accept it.

Do today

  1. Read the metadata request above against your tenant. response_modes_supported lists only query, fragment and form_post, and there is no authorization_signing_alg_values_supported.

  2. List the keys a JARM client would verify with.

curl -s "$ISSUER/oauth/jwks" | jq '.keys[] | {kid, alg, kty}'

A new tenant shows an ES256 key for access tokens and an RS256 key for ID tokens. Write down which key a client with no algorithm setting would need, and what goes wrong if the server only signs with the other.

  1. Run a form_post flow with nc -l 8765 listening and the browser's developer tools open on the Network tab. The tenant's answer to the consent step is a small HTML page with one form that submits itself, and nc shows its POST body with three fields: code, state and iss. A form_post.jwt page has the same shape with one field, response.

  2. Ask for a JARM mode by name.

curl -s -o /dev/null -w '%{redirect_url}\n' "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$(urlenc $REDIRECT)&scope=openid&state=jarm-2&response_mode=query.jwt&code_challenge=$CHALLENGE&code_challenge_method=S256"

The redirect carries error=invalid_request and the description "response_mode must be query, fragment or form_post." Audit shows oauth.authorize rejected unsupported_response_mode.

  1. See the tenant apply the rule behind the lesson's warning about query.jwt: tokens from the authorization endpoint never travel in a query string. This needs a response type that returns a token, so widen the tenant briefly. In OAuth > Flow policy, allow the implicit grant and the response type code id_token; on lab-printer, allow code id_token too.

btl-lab state    # export STATE and NONCE
curl -s -o /dev/null -w '%{redirect_url}\n' "$ISSUER/oauth/authorize?response_type=$(urlenc 'code id_token')&client_id=$CLIENT_ID&redirect_uri=$(urlenc $REDIRECT)&scope=openid&state=$STATE&nonce=$NONCE&response_mode=query&code_challenge=$CHALLENGE&code_challenge_method=S256"

The tenant refuses before any sign-in, and the error comes back in the fragment, not the query: unsupported_response_mode again.

Restore: remove code id_token from lab-printer, and remove code id_token and the implicit grant from the Flow policy.

Break it

Once G13 exists, allow code id_token again and request response_mode=query.jwt. The tenant refuses with invalid_request, because a response that carries an ID token may not travel in a query unless it is encrypted.

Restore: remove code id_token and implicit again afterwards.

Check your work

Today, Audit shows two oauth.authorize rejections with unsupported_response_mode for lab-printer, and tenant.oauth.policy.update and tenant.oauth.clients.update for the widening and its restore. Once G13 exists, Audit also shows oauth.authorize code_issued with a JWT response mode, and tenant.oauth.clients.update for the algorithm change.

Cleanup

  1. Confirm the Flow policy no longer allows implicit or code id_token, and lab-printer no longer allows code id_token.

  2. Stop nc and the listener.

Missing infrastructure

  • G13: JARM response modes, a per-client authorization_signed_response_alg with the RS256 default, signing with the tenant's existing keys, the self-submitting page for form_post.jwt, and authorization_signing_alg_values_supported in discovery. Once these exist, the Planned walkthrough runs as written.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab