OAuth2 Client Registration — Managing OAuth2 Client Metadata and Dynamic Registration
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
- What is the difference between static and dynamic client registration?
- What fields are required in client metadata?
- How is the registration access token used?
- Why must redirect URIs be validated strictly?
- How do you handle client_secret rotation?
Answers:
- Static: manual, out-of-band registration. Dynamic: automated via RFC 7591 registration endpoint.
client_nameand at least oneredirect_uris. Other fields are optional but recommended.- The registration access token authenticates the client when reading, updating, or deleting its registration.
- Open redirect vulnerabilities allow attackers to steal authorization codes by manipulating the redirect URI.
- 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
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