Separate Resource Server

Django OAuth Toolkit allows to separate the Authorization Server and the Resource Server. Based on the RFC 7662 Django OAuth Toolkit provides a rfc-compliant introspection endpoint. As well the Django OAuth Toolkit allows to verify access tokens by the use of an introspection endpoint.

Setup the Authentication Server

Setup the Authorization Server as described in the Tutorials. Create a OAuth2 access token for the Resource Server and add the introspection-Scope to the settings.

'SCOPES': {
    'read': 'Read scope',
    'write': 'Write scope',
    'introspection': 'Introspect token scope',
    ...
},

The Authorization Server will listen for introspection requests. The endpoint is located within the oauth2_provider.urls as /introspect/.

Example Request:

POST /o/introspect/ HTTP/1.1
Host: server.example.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer 3yUqsWtwKYKHnfivFcJu

token=uH3Po4KXWP4dsY4zgyxH

Example Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "active": true,
  "client_id": "oUdofn7rfhRtKWbmhyVk",
  "username": "jdoe",
  "scope": "read write dolphin",
  "exp": 1419356238,
  "aud": ["https://api.example.com", "https://data.example.com"]
}

The aud field (audience) is included when the token has resource binding per RFC 8707. Tokens without resource restrictions will not include this field.

Setup the Resource Server

Setup the Resource Server like the Authorization Server as described in the Tutorials. Add RESOURCE_SERVER_INTROSPECTION_URL and either RESOURCE_SERVER_AUTH_TOKEN or RESOURCE_SERVER_INTROSPECTION_CREDENTIALS as a (id,secret) tuple to your settings. The Resource Server will try to verify its requests on the Authorization Server.

OAUTH2_PROVIDER = {
    ...
    'RESOURCE_SERVER_INTROSPECTION_URL': 'https://example.org/o/introspect/',
    'RESOURCE_SERVER_AUTH_TOKEN': '3yUqsWtwKYKHnfivFcJu', # OR this but not both:
    # 'RESOURCE_SERVER_INTROSPECTION_CREDENTIALS': ('rs_client_id','rs_client_secret'),
    ...
}

RESOURCE_SERVER_INTROSPECTION_URL defines the introspection endpoint and RESOURCE_SERVER_AUTH_TOKEN an authentication token to authenticate against the Authorization Server. As allowed by RFC 7662, some external OAuth 2.0 servers support HTTP Basic Authentication. For these, use: RESOURCE_SERVER_INTROSPECTION_CREDENTIALS=('client_id','client_secret') instead of RESOURCE_SERVER_AUTH_TOKEN.

Token Audience Binding (RFC 8707)

Django OAuth Toolkit supports RFC 8707 Resource Indicators, which allows clients to bind access tokens to specific resource servers. This prevents tokens from being misused at unintended services.

How It Works

Clients include a resource parameter in authorization and token requests to specify which resource servers they want to access:

GET /o/authorize/?client_id=CLIENT_ID
    &response_type=code
    &redirect_uri=https://client.example.com/callback
    &scope=read
    &resource=https://api.example.com

The issued access token will be bound to https://api.example.com and should only be accepted by that resource server.

Validating Token Audiences

Django OAuth Toolkit automatically validates token audiences when using validate_bearer_token(). By default, it uses prefix-based matching where the token’s audience URI acts as a base URI.

Automatic Validation

When a resource server validates a bearer token, DOT automatically checks if the request URI matches the token’s audience claim:

# In your Django REST Framework view or OAuth-protected endpoint
# DOT automatically validates audience - no manual check needed!

@require_oauth(['read'])
def my_api_view(request):
    # If this executes, the token is valid AND authorized for this resource
    return Response({'data': 'secret'})

The default validator uses prefix matching: a token with audience https://api.example.com/v1 will be accepted for requests to https://api.example.com/v1/users but rejected for https://api.example.com/v2/users.

Resource indicators must be absolute URIs with a scheme and host, without userinfo or fragment components (a query component is allowed per RFC 8707 but is ignored when matching). Other absolute-URI forms, such as URNs, are rejected at issuance and never match the default validator. Supporting them requires customization on both sides: a custom OAUTH2_VALIDATOR_CLASS overriding _validate_resource_uris() so the authorization server accepts them at issuance, and a custom RESOURCE_SERVER_TOKEN_RESOURCE_VALIDATOR so the resource server can match them.

Deployments Behind a Reverse Proxy

Audience validation compares the token’s resource indicators against the request URI as reconstructed by Django (request.build_absolute_uri()). If your resource server runs behind a TLS-terminating reverse proxy or load balancer, Django must be configured so the reconstructed scheme and host match the externally visible URI that clients put in the resource parameter. Otherwise resource-restricted tokens will be rejected with a scheme (http vs https) or host mismatch.

Configure the standard Django settings for proxied deployments:

# settings.py
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True  # if the proxy rewrites the Host header

and ensure your proxy sets the corresponding headers. See the Django deployment docs for the security implications of these settings.

Custom Validation Logic

You can customize the validation logic by providing your own validator function:

# myapp/validators.py
def exact_match_validator(request_uri, audiences):
    """Custom validator that requires exact audience match."""
    # No audiences = unrestricted token (backward compat)
    if not audiences:
        return True

    # Require exact match
    return request_uri in audiences

# settings.py
OAUTH2_PROVIDER = {
    'RESOURCE_SERVER_TOKEN_RESOURCE_VALIDATOR': 'myapp.validators.exact_match_validator',
}

To disable automatic validation entirely, set the validator to None:

OAUTH2_PROVIDER = {
    'RESOURCE_SERVER_TOKEN_RESOURCE_VALIDATOR': None,
}