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

Processing and validating a request

Lakeside Bank has received the printer's pushed request for a 42.50 EUR payment. Before you see anything, the bank has to decide whether the request makes sense. After you approve, it has to record exactly what you approved, tell the printer, and make sure its payment API allows that payment and nothing else.

The photo service does the same work for the album request. Each check happens in one of two places: at the authorization server, before and after you decide, or at the API, on every request.

Checking the details

All domains, identifiers, accounts, and tokens in these examples are fictional. An authorization server lists the types it supports in its metadata document:

{
  "issuer": "https://auth.lakeside-bank.example",
  "authorization_details_types_supported": [
    "payment_initiation",
    "account_information"
  ]
}

Supporting a type is not the same as allowing every client to use it. A client's registration can list the types it intends to use, and the bank can limit each client to the types it has approved for it. The printer's payment client may initiate payments, but it has no reason to ask for your account history, so a request for account_information from photo-printer-pay is refused.

Each object is then checked against its type's definition. The server must refuse the whole request with the error invalid_authorization_details if any object:

  • has a type the server does not know,
  • contains a field the type does not define,
  • has a field of the wrong kind, such as a number where the type expects a string,
  • has a value the type does not allow, or
  • leaves out a field the type requires.

Refusing an unknown field can look strict. Ignoring it would be worse: the client may believe the field limited the access, and the server would grant something different from what everyone thought was being approved.

Because the printer pushed its request, the bank returns the error directly, as JSON:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store

{
  "error": "invalid_authorization_details"
}

A request sent through the browser receives the error the way any authorization error is returned. If the printer misspelled identifier in its album request, the photo service would send your browser back to https://printer.example/oauth/callback with error=invalid_authorization_details, the attempt's state, and its iss.

Some checks have to wait until you sign in. Only then does the photo service know whether album 42 is yours. If it belongs to someone else, there is nothing for you to approve, and the request is refused.

Asking you and recording the answer

The consent screen turns the details into plain language. When a request carries scopes as well, the screen must show the combined request, not two separate ones. The bank's screen might read:

Photo Printer wants to make a payment from your account

Amount:     42.50 EUR
To:         Photo Printer
Reference:  Photo book order book-5561
Pay from:   [choose one of your accounts]

Every value on that screen came from the client, so the bank treats it as data. Text such as creditorName is escaped before it is displayed, never inserted as markup. It is also only a claim: a careful bank can show the name it holds for the receiving account, much as the Redirect URIs and client metadata lesson described for client names.

You can approve less than was requested. Had the printer asked for two albums, the photo service could let you approve only album 42. Some types also allow the server to add information while you decide, which the specification calls enrichment. Here, you choose which of your accounts pays, and the bank records that choice as part of the approval. A client knows from the type definition whether to expect enrichment.

The server stores what you approved with the grant, in the same way it stores granted scopes, so that later token requests and refreshes are judged against it.

Returning what was granted

When the printer exchanges its code, the token response must include the details that were granted and assigned to the access token:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "demo-bank-access-token-3",
  "token_type": "Bearer",
  "expires_in": 300,
  "authorization_details": [
    {
      "type": "payment_initiation",
      "locations": ["https://api.lakeside-bank.example/payments"],
      "actions": ["initiate", "status"],
      "instructedAmount": { "currency": "EUR", "amount": "42.50" },
      "creditorName": "Photo Printer",
      "creditorAccount": { "accountId": "demo-merchant-account-17" },
      "remittanceInformationUnstructured": "Photo book order book-5561"
    }
  ]
}

The account you chose to pay from is not there. The server may leave values out of the client's copy, and it should share details with each party only as far as that party needs them. The printer needs to know what it may do. It does not need your account number. The payment API does, and the bank's access tokens are opaque references, so the API receives the paying account through introspection while the printer never sees it.

The printer compares the response with its request before relying on it. If you approved less, such as one album out of two, it works with what it received, just as a client does when it is granted fewer scopes than it asked for.

A token request can also include authorization_details, to ask for a token that carries only part of the grant, much as a refresh request can ask for a narrower scope. The server checks that the grant covers the request and otherwise answers invalid_authorization_details. What "covers" means depends on the type. The specification has no general rule for comparing two objects, so each type definition says whether, for example, one action includes another.

Enforcing details at the API

The authorization server must make the approved details available to the API. In a JWT access token, the recommended place is a top-level authorization_details claim, filtered to what that API needs. An introspection response can carry the same member. The photo service's token for the album request, decoded, reads:

{
  "iss": "https://auth.photos.example",
  "sub": "user-2048",
  "aud": "https://api.photos.example",
  "client_id": "photo-printer",
  "iat": 1790845200,
  "exp": 1790845800,
  "jti": "demo-token-id-12",
  "authorization_details": [
    {
      "type": "photo_album",
      "locations": ["https://api.photos.example"],
      "actions": ["read"],
      "datatypes": ["images", "captions"],
      "identifier": "42"
    }
  ]
}

There is no scope claim, because the request used only authorization details. When the printer calls GET /albums/42/photos, the photo API works through its decision:

  1. It validates the token as usual.
  2. It looks for an object of a type it understands whose action and identifier match the request. Reading album 42 matches the photo_album object, compared exactly as written.
  3. It checks the subject's own rights: album 42 still belongs to user-2048.

A request for GET /albums/57/photos or for the whole library at GET /photos finds no matching object and is refused. So is any attempt to delete, because reading is the only action granted. Rich Authorization Requests defines no error code of its own for this, so the API uses its ordinary 403 response.

Lakeside Bank's payment API applies the same idea to a payment. When the printer submits it, the API compares the amount, the currency, and the receiving account with the approved details, and refuses anything that differs. Once the payment is accepted, the bank marks the approval as used, so a second payment with the same token fails. The status action still lets the printer check on the payment it made.

Authorization details describe what a token may do, and their locations field can say where. The next group, Resource indicators, looks at a simpler way for a client to name where a token will be used, one that works with ordinary scopes.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2A client sends an authorization_details object containing a field its type does not define. What should the authorization server do?

QUESTION 2 OF 2The printer's token carries a photo_album object for album 42 with the read action. The printer requests GET /albums/57/photos. What should the photo API do?

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

Learn identity