OAuth 2.0 — Rich Authorization Requests (RAR)
Rich Authorization Requests (RFC 9396) replace coarse-grained scopes with a structured authorization_details parameter that precisely describes what the client is authorized to do. Instead of scope=payments (which could mean 'make any payment'), the client specifies the exact payment type, amount, currency, and recipient. The AS embeds the granted authorization_details in the access token, and the resource server enforces the specific constraints — not just 'can make payments' but 'can make exactly this payment'.
The user initiates a €250 payment. The client builds a structured authorization_details JSON describing the exact payment operation and redirects the browser to the AS with it encoded in the authorization request. This is the key difference from scope-based authorization — the intent is fully specified upfront.
• RFC 9396 §2: authorization_details is a JSON array encoded as a query parameter (URL-encoded JSON string).
• For large authorization_details payloads, PAR (RFC 9126) should be used to push the request server-to-server rather than embedding it in the URL. PAR + RAR is the recommended combination for financial-grade APIs.
• The authorization_details value is what the AS will show in the consent UI — specific enough for the user to make an informed decision.
• RFC 9396 §3: If authorization_details and scope are both present, both are applied. They are complementary, not mutually exclusive.
Step 1: Initiate payment of €250
application/json
session=user_session_abc
{
"amount": "250.00",
"currency": "EUR",
"recipient": "John Smith",
"iban": "DE02100100109307118603",
"reference": "Invoice #2024-0042"
}Construction Steps
Traditional scope-based authorization for payments: scope = "payments:write" This tells the AS: "the client wants to make payments." It tells the RS nothing useful: • How much? (could be any amount) • To whom? (could be any recipient) • Which account? (not specified) • One payment or unlimited? (not specified) The RS must trust the client to stay within bounds — or implement its own out-of-band authorization system. Scope is a permission category, not a permission contract.
authorization_details = [
{
"type": "payment_initiation",
"actions": ["initiate"],
"locations": ["https://payments.example.com"],
"instructedAmount": {
"currency": "EUR",
"amount": "250.00"
},
"creditorName": "John Smith",
"creditorAccount": {
"iban": "DE02100100109307118603"
},
"remittanceInformation": "Invoice #2024-0042"
}
]
This authorization is NOT for 'make payments'.
It is ONLY for: initiate exactly one €250 payment
to DE02100100109307118603 (John Smith).
RFC 9396 §2: authorization_details is a JSON array.
Each element MUST have a type field; other fields are
type-specific and defined by the authorization type's
own specification (e.g., Open Banking, FHIR, etc.).The "type" field is the key to RAR extensibility: "payment_initiation" → Open Banking / PSD2 payments "openid_credential" → OpenID for Verifiable Credentials "account_information" → Open Banking account read "medical_record" → FHIR-based health data access "document_access" → specific document + permissions Each type defines its own schema. The AS understands the type to show an appropriate consent UI. The RS understands the type to enforce the exact operation that was authorized. authorization_details can coexist with scope in the same request — they are additive, not mutually exclusive.
Signature Base String
{ amount: 250.00 EUR, recipient: John Smith, iban: DE02100100109307118603 }Signing Key
Client builds authorization_details JSON from the payment intentSignature Output
authorization_details=[{"type":"payment_initiation", ...}] encoded in the authorization URL