Skip to content

OAuth2 Client Registration — Managing OAuth2 Client Metadata and Dynamic Registration

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about OAuth2 Client Registration. We cover key concepts, practical examples, and best practices to help you master this topic.

OAuth2 client registration defines how clients are onboarded to an authorization server, including their metadata (redirect URIs, grant types, token endpoint auth method) and optionally dynamic registration (RFC 7591) for automatic provisioning.

What You'll Learn

  • Static client registration Process
  • Dynamic client registration (RFC 7591)
  • Client metadata fields and their meanings
  • Software statements for trusted registration
  • Client registration management API

Why It Matters

Client registration is the foundation of OAuth2 security. A misconfigured client (e.g., missing redirect URI validation, wrong token auth method) compromises the entire flow. DodaTech's developer portal automates client registration with validated metadata, reducing onboarding errors by 90%.

Real-World Use

A third-party developer builds a dashboard app against DodaTech's API. They register their app through the developer portal, specifying redirect URIs, scopes, and grant types. The authorization server validates the registration, generates client credentials, and provisions the client — all in under 30 seconds.

sequenceDiagram
    participant Dev as Developer
    participant Portal as Developer Portal
    participant Auth as Authorization Server
    participant DB as Client Database

    Dev->>Portal: Submit registration form
    Portal->>Portal: Validate metadata
    Portal->>Auth: POST /register (client metadata)
    Auth->>Auth: Validate redirect URIs
    Auth->>Auth: Generate client_id + client_secret
    Auth->>DB: Store client record
    Auth-->>Portal: client_id, client_secret, registration token
    Portal-->>Dev: Display credentials
    Dev->>Dev: Store client_secret securely

Code Examples

Example 1: Static Client Registration

from dataclasses import dataclass, field
from typing import List, Optional
import secrets
import hashlib

@dataclass
class OAuthClient:
    client_id: str
    client_secret: str
    client_name: str
    redirect_uris: List[str]
    grant_types: List[str]
    token_endpoint_auth_method: str = 'client_secret_basic'
    scopes: List[str] = field(default_factory=list)
    logo_uri: Optional[str] = None
    client_uri: Optional[str] = None
    policy_uri: Optional[str] = None
    tos_uri: Optional[str] = None

class ClientRegistrationService:
    def __init__(self):
        self.clients = {}

    def register_client(self, metadata) -> OAuthClient:
        """Register a new OAuth2 client."""
        # Validate metadata
        self._validate_redirect_uris(metadata.get('redirect_uris', []))
        self._validate_grant_types(metadata.get('grant_types', []))
        self._validate_auth_method(metadata.get('token_endpoint_auth_method'))

        # Generate credentials
        client_id = self._generate_client_id()
        client_secret = self._generate_client_secret()

        client = OAuthClient(
            client_id=client_id,
            client_secret=client_secret,
            client_name=metadata['client_name'],
            redirect_uris=metadata.get('redirect_uris', []),
            grant_types=metadata.get('grant_types', ['authorization_code']),
            token_endpoint_auth_method=metadata.get(
                'token_endpoint_auth_method', 'client_secret_basic'
            ),
            scopes=metadata.get('scope', '').split(),
            logo_uri=metadata.get('logo_uri'),
            client_uri=metadata.get('client_uri')
        )

        # Store client
        self.clients[client_id] = client
        return client

    def _validate_redirect_uris(self, uris):
        for uri in uris:
            parsed = urlparse(uri)
            if parsed.scheme not in ('https',):
                raise ValueError(f"Redirect URI must use HTTPS: {uri}")
            if parsed.fragment:
                raise ValueError(f"Redirect URI must not contain fragment: {uri}")

    def _generate_client_id(self):
        return f"client_{secrets.token_hex(16)}"

    def _generate_client_secret(self):
        return secrets.token_hex(32)

# Register a new client
service = ClientRegistrationService()
client = service.register_client({
    'client_name': 'Threat Dashboard',
    'redirect_uris': ['https://dashboard.dodatech.com/callback'],
    'grant_types': ['authorization_code', 'refresh_token'],
    'token_endpoint_auth_method': 'client_secret_basic',
    'scope': 'read:threats write:remediation'
})
print(f"Client ID: {client.client_id}")
print(f"Client Secret: {client.client_secret}")

Example 2: Dynamic Client Registration (RFC 7591)

from flask import Flask, request, jsonify
import secrets
import json

app = Flask(__name__)

@app.route('/register', methods=['POST'])
def dynamic_registration():
    """RFC 7591 dynamic client registration endpoint."""
    metadata = request.get_json()

    # Validate required fields
    required = ['client_name', 'redirect_uris']
    for field in required:
        if field not in metadata:
            return jsonify({'error': f'missing_{field}'}), 400

    # Validate redirect URIs
    for uri in metadata.get('redirect_uris', []):
        if not uri.startswith('https://'):
            return jsonify({
                'error': 'invalid_redirect_uri',
                'error_description': 'Redirect URIs must use HTTPS'
            }), 400

    # Generate client credentials
    client_id = f"dyn_{secrets.token_urlsafe(24)}"
    client_secret = secrets.token_urlsafe(32)
    registration_token = secrets.token_urlsafe(32)

    # Store client
    client_data = {
        'client_id': client_id,
        'client_secret': client_secret,
        'client_id_issued_at': int(time.time()),
        'client_secret_expires_at': 0,  # Never expires
        'registration_access_token': registration_token,
        'registration_client_uri': f'/register/{client_id}',
        'client_name': metadata['client_name'],
        'redirect_uris': metadata['redirect_uris'],
        'grant_types': metadata.get('grant_types', ['authorization_code']),
        'token_endpoint_auth_method': metadata.get(
            'token_endpoint_auth_method', 'client_secret_basic'
        ),
        'scope': metadata.get('scope', '')
    }
    save_client(client_id, client_data)

    return jsonify(client_data), 201

Example 3: Registration Management API

@app.route('/register/<client_id>', methods=['GET'])
def get_client_registration(client_id):
    """Read current client registration."""
    token = request.headers.get('Authorization', '').replace('Bearer ', '')
    client = get_client(client_id)

    if not client or token != client.get('registration_access_token'):
        return jsonify({'error': 'unauthorized'}), 401

    return jsonify(client)

@app.route('/register/<client_id>', methods=['PUT'])
def update_client_registration(client_id):
    """Update client registration metadata."""
    token = request.headers.get('Authorization', '').replace('Bearer ', '')
    client = get_client(client_id)

    if not client or token != client.get('registration_access_token'):
        return jsonify({'error': 'unauthorized'}), 401

    updates = request.get_json()
    # Validate updates
    if 'redirect_uris' in updates:
        for uri in updates['redirect_uris']:
            if not uri.startswith('https://'):
                return jsonify({'error': 'invalid_redirect_uri'}), 400

    # Apply updates
    for field in ['client_name', 'redirect_uris', 'scope',
                  'token_endpoint_auth_method']:
        if field in updates:
            client[field] = updates[field]

    save_client(client_id, client)
    return jsonify(client)

@app.route('/register/<client_id>', methods=['DELETE'])
def delete_client_registration(client_id):
    """Delete client registration."""
    token = request.headers.get('Authorization', '').replace('Bearer ', '')
    client = get_client(client_id)

    if not client or token != client.get('registration_access_token'):
        return jsonify({'error': 'unauthorized'}), 401

    delete_client(client_id)
    return jsonify({'result': 'deleted'})

Common Mistakes

1. Not Validating Redirect URI Format

Redirect URIs must use HTTPS, no fragments, and match exactly. Wildcard redirect URIs are dangerous.

2. Storing Client Secrets in Plaintext

Hash client secrets before storing. The secret is shown once during registration.

3. Ignoring Registration Access Tokens

Dynamic registration returns a token for managing the registration. Store it for future updates.

4. Allowing Arbitrary Grant Types

Restrict grant types to those your authorization server supports and the client needs.

5. No Rate Limiting on Registration

Attackers can fill the client database with junk registrations. Rate limit by IP and authenticate where possible.

Practice Questions

  1. What is the difference between static and dynamic client registration?
  2. What fields are required in client metadata?
  3. How is the registration access token used?
  4. Why must redirect URIs be validated strictly?
  5. How do you handle client_secret rotation?

Answers:

  1. Static: manual, out-of-band registration. Dynamic: automated via RFC 7591 registration endpoint.
  2. client_name and at least one redirect_uris. Other fields are optional but recommended.
  3. The registration access token authenticates the client when reading, updating, or deleting its registration.
  4. Open redirect vulnerabilities allow attackers to steal authorization codes by manipulating the redirect URI.
  5. Clients register a new secret via the management API, then update their configuration. The old secret continues working during the transition window.

Challenge: Build a dynamic client registration endpoint with validation, registration access tokens, and management API. Test by registering a client, updating its redirect URIs, and deleting the registration.

FAQ

Can a client have multiple secrets?

: Yes. Support multiple secrets simultaneously to enable zero-downtime rotation.

What is a software statement?

: A JWT signed by a trusted authority that asserts metadata about the client, used in dynamic registration for automated trust.

How long should a client_secret be?

: At least 32 random characters (256 bits of entropy). Use secrets.token_hex(32) or similar.

Can I use dynamic registration without user authentication?

: Yes, but implement rate limiting and consider requiring a software statement for trust.

How do I handle registration when the authorization server is down?

: Static registration is always available. Dynamic registration requires the server, so have a fallback for critical clients.

What's Next

After registration, explore {{< ilink "OAuth" "OAuth2 Client Types" }} to understand confidential vs public clients, then configure {{< ilink "OAuth" "OAuth2 Redirect URIs" }} securely.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro