Skip to content
Ashish.
All posts
Diagram illustrating the OIDC Discovery flow and Dynamic Client Registration process.

Mastering OIDC Discovery and Dynamic Client Registration

An examination of OIDC discovery mechanisms and dynamic client registration for automated configuration.

By Ashish SrivastavaPart 6 of OpenID Connect Deep Dive Series

In traditional identity integration, developers manually copy Issuer URLs and endpoints into static configuration files. This approach breaks when Identity Providers change infrastructure or when new services require immediate provisioning in cloud environments. OpenID Connect (OIDC) solves this via Discovery, with Dynamic Client Registration (DCR) provided as an extension (RFC 7591). Part 6 of the OpenID Connect Deep Dive Series, this article examines how OIDC discovery and OIDC automation shift configuration from static files to runtime protocol conversations, enabling automated trust establishment between clients and Authorization Servers.

The Discovery Mechanism: Locating the Provider

The first step in any OIDC interaction is locating the Authorization Server without hardcoding URLs. Instead of guessing, the client queries a well-known URI defined by the /.well-known/openid-configuration endpoint relative to the Issuer URL.

Imagine a microservice named order-service deployed in a Kubernetes cluster. It needs to talk to the IdP but does not know the specific IP address or load balancer URL. The order-service constructs the URL: https://auth.example.com/.well-known/openid-configuration. It performs an HTTP GET request.

The Authorization Server responds with a JSON document containing keys like authorization_endpoint, token_endpoint, jwks_uri, and crucially, issuer.

{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/oauth/authorize",
  "token_endpoint": "https://auth.example.com/oauth/token",
  "userinfo_endpoint": "https://auth.example.com/userinfo",
  "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
  "response_types_supported": [
    "code",
    "token",
    "id_token",
    "code token",
    "code id_token",
    "code token id_token"
  ]
}

The mechanism relies on the client trusting the DNS resolution of the issuer domain. The issuer claim in the JSON must match the base URL used to fetch the document. If a client fetches this from https://auth.example.com but the JSON claims the issuer is https://evil.com, the client must reject the configuration. This prevents DNS rebinding attacks where an attacker points a domain name to a malicious server mimicking the IdP's response structure.

The client parses this JSON and extracts the specific endpoints required for the current workflow. If the response_types_supported list does not include code, the client knows it cannot perform an Authorization Code Flow. This dynamic lookup allows the IdP to move its infrastructure, rotate load balancers, or migrate regions without requiring the client to update its deployment artifacts. The client simply re-fetches the configuration on startup or via a scheduled cache refresh.

Dynamic Client Registration: Automating Identity

Once the client knows where to go, it often needs to prove who it is. In legacy OAuth2 setups, every application had a client_id and client_secret provisioned manually by an administrator. If you have 500 microservices, that is 500 database entries to manage. Dynamic Client Registration (DCR) removes this administrative overhead by allowing the client to register itself at runtime.

The registration_endpoint is another key found in the discovery document. If present, it indicates the server supports DCR. The client sends a POST request to this endpoint. The body of this request is a JSON object describing the client's requirements. Note that /oauth/register in the example below is an implementation path; the actual endpoint is dynamically discovered via the registration_endpoint field in the OIDC configuration JSON as defined in RFC 7591.

Consider a new service, notification-worker, spinning up in a serverless function. It needs to authenticate users. Instead of waiting for an admin to create a client entry, the worker sends a registration request.

POST /oauth/register HTTP/1.1
Host: auth.example.com
Content-Type: application/json
 
{
  "redirect_uris": [
    "https://notification-worker.example.com/callback",
    "https://notification-worker.example.com/async-callback"
  ],
  "grant_types": [
    "authorization_code",
    "refresh_token"
  ],
  "response_types": [
    "code"
  ],
  "application_type": "web",
  "client_name": "Notification Worker Service",
  "contacts": [
    "devops@example.com"
  ],
  "scope": "openid profile email",
  "token_endpoint_auth_method": "client_secret_post"
}

The Authorization Server validates this request. It checks if the redirect_uris are allowed for the domain, if the requested scopes are permitted, and if the client is authorized to register itself. If the server allows self-registration, it generates a unique client_id and a client_secret.

The response to this POST is equally critical. The server returns the newly created credentials and metadata.

HTTP/200 OK
Content-Type: application/json
 
{
  "client_id": "s6BhdRkqt3",
  "client_secret": "7xMfz4-9B3nX",
  "registration_access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "registration_client_uri": "https://auth.example.com/oauth/register/s6BhdRkqt3",
  "client_id_issued_at": 1678886400,
  "client_secret_expires_at": 1710422400,
  "redirect_uris": [
    "https://notification-worker.example.com/callback"
  ],
  "grant_types": [
    "authorization_code"
  ]
}

The registration_access_token is a short-lived credential that allows the client to update its own metadata later without re-authenticating with the full client secret. The client_id_issued_at timestamp helps the server enforce rotation policies. The registration_client_uri provides a stable handle for future updates or deletions.

This mechanism is not just about convenience; it is about state consistency. The client stores the client_id and client_secret in its secure environment variables. On the next restart, it uses these credentials to authenticate against the token_endpoint. The flow ensures that the client's configuration is always synchronized with the server's state.

The Trust Boundary: Authentication and Validation

A common misconception is that DCR is an open door for anyone. The mechanism includes strict authentication requirements for the registration request itself. The client must include authentication credentials (e.g., client_secret or a pre-shared token) within the same request as the metadata, either in the headers or the body, rather than as a prerequisite step.

If the client is a confidential application (like a web app), it typically authenticates using a pre-existing client_id and client_secret or a private key (if using JWT assertions). If the client is public (like a native mobile app or a SPA), it cannot authenticate securely. In this case, the server often requires a separate mechanism, such as a pre-shared registration_access_token distributed out-of-band, or it may refuse self-registration entirely for public clients.

The server must also validate the client_metadata provided in the request. It does not blindly accept any redirect_uri. The server checks the URI against a whitelist of allowed domains. It ensures the grant_types requested are supported by the server's configuration. For example, if the server only supports the Authorization Code Flow, a request asking for implicit grant will be rejected.

This validation is the core of the trust model. The discovery endpoint tells the client where to go. The DCR endpoint tells the server who is coming. The server's response confirms that the client is a legitimate entity within the ecosystem.

From an automation perspective, this allows Infrastructure as Code (IaC) tools to manage identities. A Terraform script can provision a new Kubernetes namespace, spin up a pod, and have the pod automatically call the registration_endpoint to get its credentials. No human needs to touch the IdP console.

Operational Tradeoffs and Security Implications

While DCR streamlines operations, it introduces specific risks that must be managed at the mechanism level. The most significant risk is the proliferation of "rogue" clients. If the registration endpoint is too permissive, an attacker who compromises a single service account could register thousands of malicious clients and issue tokens.

To mitigate this, the Authorization Server must implement rate limiting on the registration_endpoint. It should also enforce strict policies on the redirect_uris. For instance, it might only allow URIs that end with .example.com or require the redirect_uris to be pre-approved via a separate approval flow.

Another tradeoff is the handling of secrets. In DCR, the client_secret is generated dynamically. If the client crashes before storing this secret, it loses its identity. The registration_access_token provides a recovery mechanism, allowing the client to retrieve its client_secret again if it has the token. However, this token must be treated as a credential itself. If lost, the client loses the ability to update its metadata or recover its secret.

There is also the issue of key rotation. In modern OIDC deployments, clients may use private keys (JWKs) instead of secrets. The client_metadata can include a jwks object. When a client registers, it can upload its public key. The server then validates incoming JWTs from that client against this key. This mechanism supports key rotation without changing the client_id. The client can simply update its metadata to include a new JWK, and the server will begin validating against the new key after a grace period.

The decision to enable DCR is a tradeoff between agility and control. In a tightly controlled corporate environment with few applications, manual registration might be safer. In a cloud-native environment with hundreds of ephemeral services, DCR is not just a convenience; it is a requirement for scalability.

The mechanism relies on the integrity of the JSON responses. If the discovery document is cached too aggressively, a client might continue using old endpoints after a migration. If the DCR response is intercepted, the attacker gains the client_secret. Therefore, the entire flow must be protected by TLS 1.3, and the client must validate the issuer claim strictly.

Conclusion

Ultimately, OIDC Discovery and Dynamic Client Registration transform identity configuration from a static, manual process into a dynamic, protocol-driven interaction. They allow applications to discover their identity provider and register themselves automatically, reducing the attack surface associated with hardcoded credentials and enabling true infrastructure automation.

For the developer, this means writing code that queries the well-known endpoint, parses the JSON, and handles the registration response. For the operator, it means configuring the server to accept these requests securely, validating the metadata, and managing the lifecycle of the generated clients. The result is a system where identity is not a configuration file, but a living state maintained by the protocol itself.

Common Pitfalls

When implementing DCR, teams frequently stumble on specific security and operational traps. First, rogue client proliferation occurs when the registration_endpoint lacks strict rate limiting or domain validation, allowing compromised accounts to spin up malicious services. Second, secret loss happens if a client fails to persist the client_secret immediately upon registration; without the registration_access_token, recovery is impossible. Third, weak validation logic leads to accepting arbitrary redirect_uris, which can facilitate phishing attacks or token interception if the server does not enforce a strict whitelist.

Practical Takeaways

To navigate these complexities, adopt these mental models. First, treat the registration_endpoint as a high-value target requiring the same scrutiny as your token_endpoint. Second, assume that any client capable of self-registration must have its secrets rotated automatically, not manually. Third, remember that OIDC automation shifts the burden of configuration from static files to runtime validation; ensure your monitoring captures failures in the discovery and registration flows.

FAQ

Q: Can I disable DCR for public clients? A: Yes, and it is often recommended. Public clients (like SPAs) cannot securely authenticate to the registration_endpoint without exposing secrets. Most implementations require a pre-shared registration_access_token for these clients or disable self-registration entirely for them.

Q: How aggressive should discovery document caching be? A: Be conservative. While caching reduces latency, aggressive caching can prevent clients from seeing updated endpoints during an IdP migration. A short TTL (e.g., 5 minutes) or an event-driven refresh is preferred over long-term caching.

Q: Does DCR support key rotation automatically? A: Yes, but it requires configuration. By including a jwks object in the registration request, a client can upload its public key. The server can then validate JWTs against this key. Rotation involves updating the metadata with a new JWK, which the server validates after a grace period.

Related posts