08-rich-authorization-requests

command
v0.0.78 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 27, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

08: Rich Authorization Requests (RFC 9396)

Non-UI | No infrastructure needed | Builds on all previous examples

What you'll learn

  • Start auth server with RAR support + two resource servers — The auth server advertises authorization_details_types_supported in its discovery document. Two resource servers enforce different authorization types.
  • Discover supported authorization_details types — RFC 9396 §10: the AS advertises which authorization_details types it supports. Clients check this before requesting.
  • Register a banking app via DCR (RFC 7591) — Using standards-compliant DCR (Example 06) instead of the proprietary endpoint. A banking app should be fully standards-based.
  • Request a token with payment authorization_details — The token request includes structured authorization_details — not just 'scope=payments' but the exact payment to initiate. The AS validates, embeds in the JWT, and echoes in the response.
  • Access the Payments API (authorized) — The Payments API uses RequireAuthorizationDetails middleware — it checks that the token has a payment_initiation authorization_details entry. The details are available in the request context.
  • Access the Accounts API with a payment token (rejected) — The payment token has type=payment_initiation but the Accounts API requires type=account_information. Fine-grained enforcement: a payment token can't read account data.
  • Introspect the payment token — Introspection returns the authorization_details alongside the standard claims. Resource servers that use introspection (instead of local JWT validation) get the same fine-grained information.
  • RAR coexists with scopes — Scopes and authorization_details are independent — you can use both in the same request. Scopes for coarse-grained access, RAR for fine-grained.
  • Cross-server RAR validation (optional — RAR test issuer) — No open-source IdP supports RFC 9396 on standard OAuth flows yet. We built a RAR test issuer (cmd/rar-test-issuer) for interop testing. Run 'make uprar' to start it. When Keycloak adds RAR support, this step will migrate to KC.

Flow

sequenceDiagram
    participant App as Banking App
    participant AS as Auth Server
    participant Pay as Payments API
    participant Acct as Accounts API

    Note over App,Acct: Step 1: Start auth server with RAR support + two resource servers

    Note over App,Acct: Step 2: Discover supported authorization_details types
    App->>AS: GET /.well-known/openid-configuration
    AS-->>App: {..., authorization_details_types_supported: [...]}

    Note over App,Acct: Step 3: Register a banking app via DCR (RFC 7591)
    App->>AS: POST /apps/dcr {client_name, grant_types, scope}
    AS-->>App: {client_id, client_secret}

    Note over App,Acct: Step 4: Request a token with payment authorization_details
    App->>AS: POST /api/token {authorization_details: [{type: payment_initiation, ...}]}
    AS-->>App: {access_token, authorization_details: [{type: payment_initiation, ...}]}

    Note over App,Acct: Step 5: Access the Payments API (authorized)
    App->>Pay: POST /payments (Bearer: payment token)
    Pay->>Pay: RequireAuthorizationDetails("payment_initiation") ✓
    Pay-->>App: 200 {status: payment_accepted, details: [...]}

    Note over App,Acct: Step 6: Access the Accounts API with a payment token (rejected)
    App->>Acct: GET /accounts (Bearer: payment token)
    Acct->>Acct: RequireAuthorizationDetails("account_information") ✗
    Acct-->>App: 401 Unauthorized

    Note over App,Acct: Step 7: Introspect the payment token
    RS->>AS: POST /oauth/introspect {token}
    AS-->>RS: {active: true, authorization_details: [...]}

    Note over App,Acct: Step 8: RAR coexists with scopes
    App->>AS: POST /api/token {scope: read, authorization_details: [...]}
    AS-->>App: {scope: read, authorization_details: [...]}

    Note over App,Acct: Step 9: Cross-server RAR validation (optional — RAR test issuer)
    App->>AS: POST {RAR issuer}/api/token {authorization_details}
    App->>RS: Bearer token → validate via JWKS from RAR issuer

Steps

About this example

Actors: Banking App, Auth Server (AS), Payments API (RS), Accounts API (RS). Think: a fintech app that needs to initiate a specific payment — not just "access payments". What are these?

The problem with scopes:

scope=payments              ← "can do anything with payments" (too broad)
scope=payments:initiate     ← better, but can't express amount/recipient
scope=payments:initiate:45EUR:merchant-a  ← this is getting silly

RFC 9396 solution — authorization_details:

{
  "type": "payment_initiation",
  "actions": ["initiate"],
  "instructedAmount": {"currency": "EUR", "amount": "45.00"},
  "creditorName": "Merchant A"
}

Structured, typed, with API-specific extension fields. This is what banks, fintechs, and any regulated industry needs.

Step 1: Start auth server with RAR support + two resource servers

References: RFC 9396 — Rich Authorization Requests, RFC 8414 — AS Metadata Discovery

The auth server advertises authorization_details_types_supported in its discovery document. Two resource servers enforce different authorization types.

Step 2: Discover supported authorization_details types

References: RFC 9396 — Rich Authorization Requests, RFC 8414 — AS Metadata Discovery

RFC 9396 §10: the AS advertises which authorization_details types it supports. Clients check this before requesting.

Step 3: Register a banking app via DCR (RFC 7591)

References: RFC 7591 — Dynamic Client Registration

Using standards-compliant DCR (Example 06) instead of the proprietary endpoint. A banking app should be fully standards-based.

Step 4: Request a token with payment authorization_details

References: RFC 9396 — Rich Authorization Requests, RFC 6749 §4.4 — Client Credentials Grant

The token request includes structured authorization_details — not just 'scope=payments' but the exact payment to initiate. The AS validates, embeds in the JWT, and echoes in the response.

What's in the JWT?

The access token now carries authorization_details as a JWT claim:

{
  "sub": "app_abc123",
  "scopes": [...],
  "authorization_details": [
    {
      "type": "payment_initiation",
      "actions": ["initiate"],
      "instructedAmount": {"currency": "EUR", "amount": "45.00"},
      "creditorName": "Merchant A"
    }
  ]
}

The resource server reads this claim to enforce fine-grained access.

Step 5: Access the Payments API (authorized)

References: RFC 9396 — Rich Authorization Requests

The Payments API uses RequireAuthorizationDetails middleware — it checks that the token has a payment_initiation authorization_details entry. The details are available in the request context.

Step 6: Access the Accounts API with a payment token (rejected)

References: RFC 9396 — Rich Authorization Requests

The payment token has type=payment_initiation but the Accounts API requires type=account_information. Fine-grained enforcement: a payment token can't read account data.

Step 7: Introspect the payment token

References: RFC 9396 — Rich Authorization Requests, RFC 7662 — Token Introspection

Introspection returns the authorization_details alongside the standard claims. Resource servers that use introspection (instead of local JWT validation) get the same fine-grained information.

Step 8: RAR coexists with scopes

References: RFC 9396 — Rich Authorization Requests

Scopes and authorization_details are independent — you can use both in the same request. Scopes for coarse-grained access, RAR for fine-grained.

Step 9: Cross-server RAR validation (optional — RAR test issuer)

References: RFC 9396 — Rich Authorization Requests

No open-source IdP supports RFC 9396 on standard OAuth flows yet. We built a RAR test issuer (cmd/rar-test-issuer) for interop testing. Run 'make uprar' to start it. When Keycloak adds RAR support, this step will migrate to KC.

RFC 9396 in OneAuth — what we built
Layer What RFC section
Data model core.AuthorizationDetail with JSON flattening §2
Token endpoint Parse, validate, embed in JWT, return in response §5
Form-encoded authorization_details as JSON string in form params §6.1
Introspection Include in introspection response §9.1
AS metadata authorization_details_types_supported §10
DCR authorization_details_types on client registration §10
Middleware RequireAuthorizationDetails() enforcement §2
Client SDK AuthorizationDetails on token requests/responses §5
Error handling invalid_authorization_details error code §5.2
What's next?

In 09 — Key Rotation, you'll see how to rotate signing keys with a grace period — old tokens keep working during the transition, then fail after the grace window closes.

References

Run it

go run ./examples/08-rich-authorization-requests/

Pass --non-interactive to skip pauses:

go run ./examples/08-rich-authorization-requests/ --non-interactive

Documentation

Overview

Example 08: Rich Authorization Requests (RFC 9396)

This is the culmination of Examples 01-07. Flat scopes like "read" and "write" can't express "transfer 45 EUR to Merchant A". RFC 9396 adds structured authorization_details to OAuth — fine-grained, typed, with API-specific fields.

This is the feature that motivated this entire PR — driven by a banking client evaluating OneAuth for transaction-level authorization.

Run: go run ./examples/08-rich-authorization-requests/ Docs: Run with --readme to regenerate README.md

See: https://www.rfc-editor.org/rfc/rfc9396

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL