Skip to content

OAuth2 Rich Authorization Requests (RAR) — RFC 9396 for Structured Authorization Details

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about OAuth2 Rich Authorization Requests (RAR). We cover key concepts, practical examples, and best practices to help you master this topic.

OAuth2 Rich Authorization Requests (RAR, RFC 9396) extend authorization requests beyond simple scope strings, allowing clients to request structured authorization details — specific resources, actions, locations, and constraints — as JSON objects.

What You'll Learn

  • Authorization details as structured JSON
  • RAR in authorization and token requests
  • Combining scopes with authorization details
  • Consent display for rich requests
  • Validating and enforcing authorization details

Why It Matters

Scope strings are too coarse for fine-grained authorization. A scope like read:threats gives access to all threats. RAR lets a client request access to specific threat IDs in a specific tenant with specific actions. DodaTech's API uses RAR to grant partners access to exactly the threat feeds they subscribe to, nothing more.

Real-World Use

A partner integration requests access to read threat indicators for two specific campaigns in a specific region. Instead of the broad read:threats scope, the RAR specifies type: campaign-iocs, campaign_ids: [camp-1, camp-2], and region: apac. The consent screen shows exactly this granular detail.

sequenceDiagram
    participant Client as Partner App
    participant Auth as Authorization Server
    participant API as Threat API

    Client->>Auth: Authorization Request + RAR
    Note over Client,Auth: authorization_details: [{type: "campaign-iocs", campaigns: ["camp-1","camp-2"], region: "apac"}]

    Auth->>Auth: Validate RAR structure
    Auth->>User: Consent screen shows granular details
    User-->>Auth: Approve
    Auth-->>Client: Token with RAR in token

    Client->>API: Request + Token (with RAR)
    API->>API: Enforce RAR: only camp-1, camp-2, region=apac
    API-->>Client: Filtered results

Code Examples

Example 1: Authorization Request with RAR

import json
import requests

def build_rar_authorization_url(client_id, redirect_uri, auth_server,
                                authorization_details, state=None):
    """
    Build authorization URL with Rich Authorization Requests.
    Per RFC 9396, authorization_details is a JSON array of objects.
    """
    params = {
        'response_type': 'code',
        'client_id': client_id,
        'redirect_uri': redirect_uri,
        'authorization_details': json.dumps(authorization_details),
        'state': state or secrets.token_urlsafe(16)
    }

    auth_url = f"{auth_server}/authorize?{urlencode(params)}"
    return auth_url

# Example RAR for accessing specific threats
details = [
    {
        'type': 'threat-analysis',
        'actions': ['read', 'export'],
        'locations': ['threat-123', 'threat-456'],
        'datatypes': ['indicators', 'reports']
    },
    {
        'type': 'remediation',
        'actions': ['read'],
        'locations': ['campaign-campaign-789']
    }
]

auth_url = build_rar_authorization_url(
    CLIENT_ID,
    'https://app.dodatech.com/callback',
    'https://auth.dodatech.com',
    details
)
print(f"Authorize URL length: {len(auth_url)} chars")

Example 2: RAR Validation in Authorization Server

from flask import Flask, request, jsonify
import json

app = Flask(__name__)

# RAR type definitions
RAR_TYPES = {
    'threat-analysis': {
        'actions': ['read', 'write', 'export', 'delete'],
        'properties': {
            'locations': {'type': 'array', 'items': 'string'},
            'datatypes': {
                'type': 'array',
                'items': {'type': 'string',
                         'enum': ['indicators', 'reports', 'raw_data']}
            }
        }
    },
    'remediation': {
        'actions': ['read', 'write', 'execute'],
        'properties': {
            'locations': {'type': 'array', 'items': 'string'}
        }
    }
}

def validate_authorization_details(details):
    """Validate RAR authorization_details array."""
    if not isinstance(details, list):
        return False, "authorization_details must be an array"

    for idx, entry in enumerate(details):
        type_def = RAR_TYPES.get(entry.get('type'))
        if not type_def:
            return False, f"Unknown authorization detail type: {entry.get('type')}"

        # Validate actions
        for action in entry.get('actions', []):
            if action not in type_def['actions']:
                return False, f"Invalid action '{action}' for type '{entry['type']}'"

        # Validate properties
        for prop, value in entry.items():
            if prop in ('type', 'actions'):
                continue
            prop_def = type_def['properties'].get(prop)
            if not prop_def:
                return False, f"Unknown property '{prop}' for type '{entry['type']}'"

    return True, "valid"

@app.route('/authorize', methods=['GET'])
def authorize_with_rar():
    auth_details = request.args.get('authorization_details')

    if auth_details:
        try:
            details = json.loads(auth_details)
            valid, msg = validate_authorization_details(details)
            if not valid:
                return jsonify({'error': 'invalid_authorization_details',
                              'error_description': msg}), 400

            # Store RAR for consent and token generation
            session['authorization_details'] = details

        except json.JSONDecodeError:
            return jsonify({'error': 'invalid_authorization_details'}), 400

    # Continue with standard auth flow...
    return render_consent(client_id, scope, authorization_details=details)

Example 3: RAR Enforcement in Resource Server

from flask import Flask, request, jsonify, g
from functools import wraps

app = Flask(__name__)

def enforce_rar(type_name, required_action=None):
    """Decorator to enforce RAR authorization details on endpoints."""
    def decorator(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            token = extract_token(request)
            rar_details = token.get('authorization_details', [])

            # Find matching RAR entry
            matching = [
                d for d in rar_details
                if d['type'] == type_name
            ]

            if not matching:
                return jsonify({'error': 'insufficient_authorization',
                              'required_type': type_name}), 403

            entry = matching[0]

            # Check action
            if required_action and required_action not in entry.get('actions', []):
                return jsonify({'error': 'action_not_authorized',
                              'required_action': required_action}), 403

            # Store RAR context for the handler
            g.rar_context = entry
            return f(*args, **kwargs)
        return decorated
    return decorator

@app.route('/api/threats/<threat_id>')
@enforce_rar('threat-analysis', required_action='read')
def get_threat(threat_id):
    """Get threat data, filtered by RAR locations."""
    rar = g.rar_context
    allowed_locations = rar.get('locations', [])

    threat = get_threat_data(threat_id)

    # Enforce location restrictions
    if allowed_locations and threat_id not in allowed_locations:
        return jsonify({'error': 'location_not_authorized'}), 403

    # Enforce datatype restrictions
    allowed_datatypes = rar.get('datatypes', ['indicators', 'reports'])
    filtered = {
        k: v for k, v in threat.items()
        if k in allowed_datatypes
    }

    return jsonify(filtered)

@app.route('/api/threats/<threat_id>/remediate', methods=['POST'])
@enforce_rar('remediation', required_action='execute')
def remediate_threat(threat_id):
    """Execute remediation, if authorized by RAR."""
    rar = g.rar_context
    return jsonify({'status': 'remediated', 'threat_id': threat_id})

Common Mistakes

1. Using String Scopes Instead of Structured Details

RAR replaces or augments scopes. Don't define authorization details as a single string — use proper JSON arrays.

2. Not Defining RAR Types in Advance

Each authorization detail type must be defined in the authorization server's documentation and supported by resource servers.

3. Ignoring RAR in Token Responses

Include authorization_details in token introspection responses and JWT claims so resource servers can enforce them.

4. Making RAR Too Complex

Balance granularity with usability. Very complex RAR structures confuse users on consent screens.

5. Not Validating RAR at Token Exchange

Validate authorization_details at the token endpoint, not just the authorization endpoint.

Practice Questions

  1. What does RFC 9396 define?
  2. How does RAR differ from OAuth2 scopes?
  3. What fields should each authorization detail object contain?
  4. How do resource servers enforce RAR?
  5. How is RAR displayed on consent screens?

Answers:

  1. RFC 9396 defines Rich Authorization Requests — structured JSON authorization details beyond simple scopes.
  2. Scopes are coarsely named strings. RAR provides structured objects with typed fields, actions, and locations for fine-grained authorization.
  3. At minimum type and actions. Optional fields depend on the type definition (e.g., locations, datatypes).
  4. Resource servers parse the authorization_details from the token, match the type, and enforce action and location constraints.
  5. Each authorization detail object is displayed as a separate line item with its type, requested actions, and specific resources.

Challenge: Design an RAR system for a document management API with types like document:read, document:write, admin:users. Implement validation on the authorization server and enforcement on the resource server.

FAQ

Does RAR replace OAuth2 scopes?

: No. RAR augments scopes. You can combine scope with authorization_details for coarse (scope) + fine-grained (RAR) authorization.

Is RAR part of core OAuth2?

: No, it's an extension (RFC 9396). Not all authorization servers support it.

Can RAR be used with client credentials?

: Yes. Machine-to-machine flows benefit from RAR by specifying exact resources and actions.

How do I version RAR types?

: Include a version in the type name: type: "threat-analysis.v2".

Does RAR work with JWT access tokens?

: Yes. Include authorization_details as a claim in JWT access tokens for resource server enforcement.

What's Next

Combine RAR with {{< ilink "OAuth" "OAuth2 Scopes" }} for multi-level authorization, and explore {{< ilink "OAuth" "OAuth2 Claims" }} for structured token claims.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro