Dynamic Client Registration

Django OAuth Toolkit includes support for the OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591) and the OAuth 2.0 Dynamic Client Registration Management Protocol (RFC 7592).

These views are automatically available when you use include("oauth2_provider.urls").

Endpoints

POST /o/register/

Creates a new OAuth2 application (RFC 7591). Authentication is controlled by DCR_REGISTRATION_PERMISSION_CLASSES.

Request body (JSON):

{
  "redirect_uris": ["https://example.com/callback"],
  "grant_types": ["authorization_code"],
  "client_name": "My Application",
  "token_endpoint_auth_method": "client_secret_basic"
}

Response (201):

{
  "client_id": "abc123",
  "client_secret": "...",
  "redirect_uris": ["https://example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "client_secret_basic",
  "client_name": "My Application",
  "registration_access_token": "...",
  "registration_client_uri": "https://example.com/o/register/abc123/"
}

Applications created through this endpoint are flagged with dcr_created=True on the Application model, so dynamically registered clients can be distinguished from manually provisioned ones — the Django admin’s application list can be filtered on this field.

GET/PUT/DELETE /o/register/{client_id}/

Read, update, or delete the client configuration (RFC 7592). Requires a Bearer {registration_access_token} header issued during registration.

  • GET — returns current client metadata (same format as the registration response, except client_secret, which is only returned once on the initial POST since it is hashed at rest and cannot be recovered afterward)

  • PUT — full replacement of the client metadata (RFC 7592 section 2.2): accepts the same JSON body as POST and must include every metadata field the client wants to keep. Omitted fields are reset to their registration defaults — for example, an omitted token_endpoint_auth_method reverts the client to confidential and an omitted client_name clears the name. Read the current configuration with GET first, modify it, and send the complete document back.

  • DELETE — deletes the application and all associated tokens; returns 204

Field Mapping

RFC 7591 field

DOT Application field

Notes

redirect_uris (array)

redirect_uris (space-joined)

client_name

name

grant_types (array)

authorization_grant_type

refresh_token is ignored; only one non-refresh grant type is supported per application

token_endpoint_auth_method: none

client_type = "public"

token_endpoint_auth_method: ...

client_type = "confidential"

Default

Note

client_secret_basic and client_secret_post are both accepted at registration, since DOT’s token endpoint authenticates confidential clients through either HTTP Basic auth or request-body credentials. The Application model does not record which method was requested, so per RFC 7591 section 2 (the server “MAY replace any of the client’s requested metadata values … with suitable values”) responses normalize the registered value to client_secret_basic; clients may nevertheless use either method at the token endpoint.

Configuration

Add the following keys to OAUTH2_PROVIDER in your Django settings. All are optional and have sensible defaults.

DCR_ENABLED

Set to True to activate the Dynamic Client Registration endpoints. When False (the default), both endpoints return 404 even though the URL patterns are always registered.

Default: False

DCR_REGISTRATION_PERMISSION_CLASSES

A tuple of importable class paths whose instances are instantiated and called as instance.has_permission(request) -> bool. All classes must pass (AND logic).

Default: ("oauth2_provider.dcr.IsAuthenticatedDCRPermission",)

Built-in classes:

  • oauth2_provider.dcr.IsAuthenticatedDCRPermission — requires Django session authentication.

  • oauth2_provider.dcr.AllowAllDCRPermission — open registration; no authentication required.

Note

The registration view itself is csrf_exempt so that anonymous and Authorization-header clients can POST to it. CSRF protection for session-cookie-authenticated requests is enforced by IsAuthenticatedDCRPermission instead: such requests must include a valid CSRF token or they are rejected. If you write a custom permission class that accepts Django session authentication, call oauth2_provider.dcr.enforce_csrf(request) for cookie-authenticated requests to keep the endpoint CSRF-protected.

DCR_REGISTRATION_SCOPE

The scope string stored on the registration AccessToken used to protect the RFC 7592 management endpoints.

Default: "oauth2_provider:registration"

DCR_REGISTRATION_TOKEN_EXPIRE_SECONDS

Number of seconds until the registration access token expires, or None for a far-future expiry (year 9999, effectively non-expiring).

Default: None

DCR_ROTATE_REGISTRATION_TOKEN_ON_UPDATE

When True, a PUT request to the management endpoint revokes the current registration access token and issues a new one, returning it in the response.

Default: True

Examples

Open registration (no auth required):

OAUTH2_PROVIDER = {
    "DCR_ENABLED": True,
    "DCR_REGISTRATION_PERMISSION_CLASSES": ("oauth2_provider.dcr.AllowAllDCRPermission",),
}

Custom permission class (e.g. initial-access token):

# myapp/permissions.py
from oauth2_provider.utils import parse_bearer_token


class InitialAccessTokenPermission:
    def has_permission(self, request) -> bool:
        # parse_bearer_token implements RFC 7235 / RFC 6750 semantics
        # (exact, case-insensitive scheme match); None means the header
        # is not a well-formed Bearer authorization.
        token = parse_bearer_token(request.META.get("HTTP_AUTHORIZATION", ""))
        if token is None:
            return False
        return MyInitialToken.objects.filter(token=token, active=True).exists()

# settings.py
OAUTH2_PROVIDER = {
    "DCR_ENABLED": True,
    "DCR_REGISTRATION_PERMISSION_CLASSES": ("myapp.permissions.InitialAccessTokenPermission",),
}

Smoke test with curl:

# Register (open mode)
curl -X POST https://example.com/o/register/ \\
  -H "Content-Type: application/json" \\
  -d '{"redirect_uris":["https://app.example.com/cb"],"grant_types":["authorization_code"]}'

# Read configuration
curl https://example.com/o/register/{client_id}/ \\
  -H "Authorization: Bearer {registration_access_token}"