CIMD for Servers
Learn how authorization servers can securely consume Client ID Metadata Documents.
With CIMD, your authorization server can fetch client metadata just-in-time from the client_id URL. In most cases, you don't even need a client registry — just fetch the metadata in real time.
CIMD Integration Flow
Receive OAuth Request
Client sends authorization request with client_id as HTTPS URL
GET /authorize?client_id=https://client.dev/oauth/metadata.json&...
Fetch CIMD Document
Make HTTPS GET request to client_id URL to retrieve metadata
GET /oauth/metadata.json HTTP/1.1 Host: client.dev Accept: application/json
Validate Schema & Content
Parse JSON, validate required fields, check redirect URIs
Enforce Policies
Apply security rules, rate limits, and organizational policies
Proceed with OAuth Flow
Continue with standard OAuth authorization flow using fetched metadata
Implementation Notes
Fetching & Caching
HTTPS Only
Never fetch metadata over HTTP - reject such client_ids immediately
Reasonable TTL
Cache metadata for 5-15 minutes to balance freshness with performance
Respect Cache-Control
Honor HTTP caching headers if provided by the client
Backoff on Failures
Implement exponential backoff when metadata fetches fail
Validation Requirements
Strict JSON Parsing
Reject malformed JSON immediately
Required Fields
Ensure client_id
matches the fetched URL and redirect_uris
is present
URI Validation
Validate all URIs are well-formed and use appropriate schemes
Redirect URI Security
Enforce HTTPS for redirect URIs and require exact matches (no wildcards)
Error Handling
Fetch Failures
If metadata fetch fails → return clear OAuth error response
{ "error": "invalid_client", "error_description": "Unable to fetch client metadata from specified URL" }
Validation Failures
If JSON is invalid or missing required fields → return descriptive error
{ "error": "invalid_client_metadata", "error_description": "Client metadata missing required 'redirect_uris' field" }
Retry Strategy
Consider implementing a retry window before permanent failure
Security Considerations
Fetching arbitrary URLs creates Server-Side Request Forgery (SSRF) risks. Implement these protections:
Network Restrictions
Allowlist egress to public IPv4/IPv6 only - block private ranges (10.0.0.0/8, 192.168.0.0/16, 127.0.0.0/8)
DNS Security
Resolve DNS once and pin IP for the request to prevent DNS rebinding attacks
Request Limits
Set tight timeouts (5-10s), limit content length (5KB), restrict redirects (max 3)
Dedicated Proxy
Use a separate egress proxy for metadata fetches, isolated from internal networks
• Block link-local addresses (169.254.0.0/16)
• Reject file:// and other non-HTTP schemes
• Validate TLS certificates
• Use modern TLS versions only
• Limit metadata fetches per client_id
• Implement per-IP rate limiting
• Use exponential backoff on failures
• Monitor for abuse patterns
Operational Guidelines
Log All Fetch Outcomes
Success, failures, validation errors with correlation IDs
Track Performance Metrics
Response times, cache hit rates, error rates by client_id
Alert on Anomalies
High failure rates, slow responses, suspicious patterns
Manual Refresh
Admin tool to force re-fetch when clients update metadata
Cache Inspection
View cached metadata and expiration times
Debugging Support
Trace metadata fetch process for troubleshooting
Consent Screen Enhancement
Display client_uri on consent screens. Show trust warnings for localhost redirects.
Implementation Pseudocode
async def process_cimd_client(client_id_url: str) -> ClientMetadata: # 1. Validate URL format if not client_id_url.startswith('https://'): raise InvalidClientError("client_id must be HTTPS URL") # 2. Check cache first cached_metadata = cache.get(client_id_url) if cached_metadata and not cached_metadata.is_expired(): return cached_metadata # 3. Fetch with security protections try: # Apply SSRF protections validated_url = ssrf_validator.validate(client_id_url) # Fetch with timeouts and limits response = await http_client.get( validated_url, timeout=10, max_size=5120, # 5KB limit follow_redirects=3 ) if response.content_type != 'application/json': raise InvalidClientMetadataError("Invalid Content-Type") except (TimeoutError, NetworkError) as e: # Log failure and return error logger.error(f"Failed to fetch CIMD", client_id=client_id_url, error=str(e)) raise InvalidClientError("Unable to fetch client metadata") # 4. Parse and validate JSON try: metadata = json.loads(response.body) validate_cimd_schema(metadata, client_id_url) except (JSONDecodeError, ValidationError) as e: logger.error(f"Invalid CIMD document", client_id=client_id_url, error=str(e)) raise InvalidClientMetadataError("Invalid metadata format") # 5. Cache and return cache.set(client_id_url, metadata, ttl=600) # 10 minutes return ClientMetadata(metadata) def validate_cimd_schema(metadata: dict, expected_client_id: str): # Ensure required fields if metadata.get('client_id') != expected_client_id: raise ValidationError("client_id mismatch") if not metadata.get('redirect_uris'): raise ValidationError("Missing redirect_uris") # Validate redirect URIs for uri in metadata['redirect_uris']: if not uri.startswith('https://'): raise ValidationError("Redirect URIs must use HTTPS")