Rich Authorization Requests (RAR)
Request fine-grained resource-level permissions using authorization_details.
Rich Authorization Requests (RFC 9396) let a client request access to specific resources — not just broad scopes. Instead of asking for uploads:read, your app can ask for read access to a specific bucket or folder by ID, and the auth server will verify the user actually has access to that resource before issuing the token.
This page covers both sides:
- App admin: registering resource types and implementing the validation callback
- Client: sending
authorization_detailsin an authorization request
How it works
When a client includes authorization_details in an authorization request, the auth server calls your app's validation callback at up to three points:
- PAR — when the request is first pushed (no user context yet). Validates that the resources exist.
- Consent — when the user approves the request. Validates the user has access to each resource, and returns display names for the consent screen.
- Token exchange — when a token is exchanged or delegated. Validates that downscoped details are acceptable.
If any validation fails, the authorization request is rejected with invalid_authorization_details.
The resulting access token contains the approved authorization_details as a claim, which your resource server can inspect.
App admin: setting up RAR
1. Register resource types
Each resource type defines the shape of an authorization_details entry your app accepts. Configure these on your application via the Xeonr Auth UI:
| Field | Description |
|---|---|
type | Short name for this resource (e.g. bucket). Clients use app-slug:type |
json_schema | JSON Schema that validates entries of this type |
display_title_field | Which response field your callback returns as the display title |
display_description_field | Which response field your callback returns as the display description |
2. Register a validation callback URL
Set validation_callback_url on your application via the Xeonr Auth UI. The auth server will POST to this URL at each validation phase.
3. Implement the callback
The auth server sends a signed JWT as a Bearer token in each callback request — verify it before processing.
Verifying the JWT:
- Fetch the auth server's JWKS from
https://auth.xeonr.io/.well-known/jwks.json - Verify the signature using RS256
- Check
issisauth.xeonr.io,audis your application's external ID, andexphasn't passed - Check
scopematches the expected phase (see below)
Request from the auth server:
POST /api/auth/validate HTTP/1.1
Host: your-app.example.com
Authorization: Bearer eyJ... (signed by auth server, expires in 30s)
Content-Type: application/json
{
"phase": "VALIDATION_PHASE_PAR",
"authorizationDetails": [
{
"type": "uploads:bucket",
"bucket_id": "bucket-123",
"permissions": ["read", "write"]
}
],
"clientId": "550e8400-e29b-41d4-a716-446655440000",
"subject": "",
"scopes": []
}Phases and what they receive:
| Phase | scope in JWT | subject | When called |
|---|---|---|---|
VALIDATION_PHASE_PAR | rar:validate:par | Empty | At PAR time |
VALIDATION_PHASE_CONSENT | rar:validate:consent | User URN | When user approves |
VALIDATION_PHASE_EXCHANGE | rar:validate:exchange | User URN | At token exchange/delegation |
During VALIDATION_PHASE_EXCHANGE, the request also includes:
originalAuthorizationDetails— details from the source tokenactorClientId— the client performing the delegation
Response format:
Return one ValidatedDetail entry per entry in authorizationDetails, in the same order:
{
"details": [
{
"type": "uploads:bucket",
"approved": true,
"fields": {
"title": "My Photos",
"description": "234 files"
}
},
{
"type": "uploads:bucket",
"approved": false,
"error": "bucket not found"
}
]
}| Field | Type | Description |
|---|---|---|
type | string | Must match the entry's type |
approved | boolean | Whether access is granted |
fields | object | Display fields shown on the consent screen |
error | string | Reason for rejection (shown to the auth server, not the end user) |
Timeout: The auth server waits up to 10 seconds for your callback. A non-2xx response or timeout causes the authorization request to fail with HTTP 503.
Client: sending authorization_details
Include authorization_details as a JSON-encoded array in your authorization request. We recommend using PAR so the full payload isn't exposed in the browser's URL bar.
Using PAR
Push the authorization request first:
POST /api/v1/oauth/par HTTP/1.1
Host: auth.xeonr.io
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
response_type=code
&client_id=550e8400-e29b-41d4-a716-446655440000
&redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback
&scope=openid%20uploads%3Aread
&state=abc123
&authorization_details=%5B%7B%22type%22%3A%22uploads%3Abucket%22%2C%22bucket_id%22%3A%22bucket-123%22%2C%22permissions%22%3A%5B%22read%22%5D%7D%5DDecoded authorization_details:
[
{
"type": "uploads:bucket",
"bucket_id": "bucket-123",
"permissions": ["read"]
}
]PAR response (HTTP 201):
{
"request_uri": "urn:ietf:params:oauth:request_uri:550e8400-e29b-41d4-a716-446655440000",
"expires_in": 600
}PAR endpoint parameters:
| Parameter | Required | Description |
|---|---|---|
response_type | Yes | code |
client_id | Yes | Your client UUID |
redirect_uri | Yes | Registered redirect URI |
scope | Yes | Space-separated scopes |
state | Yes | CSRF protection value |
authorization_details | Yes | JSON array of resource requests |
code_challenge | No | PKCE challenge |
code_challenge_method | No | S256 or plain |
Redirect using the request_uri
Use the returned request_uri instead of inline parameters:
https://auth.xeonr.io/auth/authorize
?client_id=550e8400-e29b-41d4-a716-446655440000
&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3A550e8400-e29b-41d4-a716-446655440000The rest of the flow (code exchange, token use) is identical to the Authorization Code guide.
authorization_details in the token
Once issued, the access token includes the approved details as a claim:
{
"sub": "urn:xeonr:user:12345",
"authorization_details": [
{
"type": "uploads:bucket",
"bucket_id": "bucket-123",
"permissions": ["read"]
}
]
}Your resource server should validate this claim to enforce access at the resource level. The same data is returned by the introspection endpoint.