
Implementing and Validating Discovery in Your Client
A technical walkthrough for backend developers on implementing OAuth 2.1 discovery, issuer validation, and strict discovery document validation using OpenIDConnectConfigurationRetriever.
In OAuth 2.1 security, clients must rigorously validate Authorization Server metadata rather than assuming trust. Discovery, defined in RFC 8414, provides the mechanism for this by returning base URIs for endpoints and keys. If a client accepts these endpoints without validating their origin against the issuer, it creates a vector for authorization server substitution.
The Mechanism of Trust: RFC 8414 Discovery
Discovery is a JSON document located at a well-known URI (typically /.well-known/openid-configuration or /.well-known/oauth-authorization-server). The authorization server metadata provides the client with the base URIs for authorization, token issuance, and key rotation (JWKS).
The core problem in naive implementations is the assumption that the issuer field in the JSON matches the URL used to fetch it. An attacker who controls a DNS entry or can perform a Man-in-the-Middle (MITM) attack could return a discovery document where the issuer says https://legit-provider.com, but the authorization_endpoint points to https://attacker-controlled-phish.com/auth. If the client blindly follows these links, the user’s credentials are stolen.
This process is known as strictdiscoverydocumentvalidation in many SDKs. The client must perform two distinct validations:
- Issuer Validation: Ensuring the
issclaim matches the request origin. - Endpoint Validation: Ensuring all returned URIs belong to the same authority as the issuer.
Issuer Validation Logic
The issuer parameter is the anchor of trust. According to OpenID Connect Core and RFC 8414, the client MUST validate that the iss value in the discovery response exactly matches the URL used to retrieve the document, after normalizing for scheme, host, and port.
The Normalization Problem
Developers often make the mistake of string-matching URLs. This fails because:
http://example.comandhttps://example.comare different schemes.http://example.comandhttp://example.com:80are effectively the same, but string comparison treats them differently.- Case sensitivity in the host (though DNS is case-insensitive, strict validation often lowercases hosts) can lead to bypasses.
The correct mechanism is to parse the request URI and the iss claim as standardized URL objects. The validation logic should enforce:
- Scheme Match:
httpsmust matchhttps. Downgrade attacks from HTTP to HTTPS in the issuer claim must be rejected if the discovery endpoint was fetched over HTTPS. - Host Match: The hostname must be identical.
- Port Match: If the port is specified, it must match. If omitted, default ports (80/443) are assumed, but explicit ports must be respected.
def validate_issuer(request_uri: str, issuer_claim: str) -> bool:
req_url = urllib.parse.urlparse(request_uri)
iss_url = urllib.parse.urlparse(issuer_claim)
# 1. Scheme must match (preferably both HTTPS)
if req_url.scheme != iss_url.scheme:
return False
# 2. Host must match (case-insensitive for DNS safety)
if req_url.hostname.lower() != iss_url.hostname.lower():
return False
# 3. Port must match (if explicitly present in request)
req_port = req_url.port if req_url.port else (443 if req_url.scheme == 'https' else 80)
iss_port = iss_url.port if iss_url.port else (443 if iss_url.scheme == 'https' else 80)
if req_port != iss_port:
return False
return TrueThis logic prevents an attacker from returning a discovery document from https://evil.com that claims iss=https://legit.com. The mismatch in host triggers a rejection.
Strict Discovery Document Validation
Beyond the issuer, RFC 8414 Section 3 mandates that the client validate the endpoints returned in the discovery document. This is where many libraries fail by exposing a raw OpenIDConnectConfigurationRetriever without enforcing origin constraints. The OpenIDConnectConfigurationRetriever class handles this retrieval and validation.
The client must verify that every endpoint URI (authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint) shares the same authority (scheme + host + port) as the issuer.
Why This Matters
Consider an AS that is compromised. The attacker modifies the discovery response:
issuer:https://auth.example.comtoken_endpoint:https://attacker.evil.com/token
If the client does not validate the origin of the token endpoint, it will send the authorization code to the attacker’s server. The attacker then exchanges this code for an access token, gaining full control of the user’s session.
Implementing the Validator
The OpenIDConnectConfigurationRetriever pattern should encapsulate this validation. When the configuration is retrieved, the validator must iterate through all endpoint fields and assert that their origin matches the issuer’s origin.
// Conceptual C# logic for Microsoft.IdentityModel.Protocols.OpenIdConnect
// This is the mechanism you must ensure is enabled.
var config = await OpenIdConnectConfigurationRetriever.GetAsync(
discoveryDocumentAddress,
httpHandler,
cancellationToken);
// Implicitly, the library should validate that:
// config.AuthorizationEndpoint.Authority == discoveryDocumentAddress.Authority
// config.TokenEndpoint.Authority == discoveryDocumentAddress.Authority
// config.JwkSetUri.Authority == discoveryDocumentAddress.AuthorityIf any endpoint’s authority differs from the issuer’s authority, the retrieval must throw a SecurityTokenInvalidIssuerException or equivalent. This is not optional. It is the primary defense against endpoint redirection attacks.
Worked Scenario: The Malicious Redirect
Let’s walk through a concrete attack and how strict validation stops it.
Actor A: ClientApp (Your Backend)
Actor B: LegitAS (https://auth.example.com)
Actor C: Attacker (https://evil.com)
-
Discovery Request:
ClientApprequestshttps://auth.example.com/.well-known/openid-configuration. -
MITM Attack:
Attackerintercepts the request (or compromises DNS) and returns a modified JSON response:{ "issuer": "https://auth.example.com", "authorization_endpoint": "https://auth.example.com/auth", "token_endpoint": "https://evil.com/token", "jwks_uri": "https://auth.example.com/.well-known/jwks.json" } -
Naive Client Behavior: The client parses the JSON. It sees
issuermatches the request URL. It proceeds to use thetoken_endpoint. -
The Breach: The client sends the authorization code to
https://evil.com/token. The Attacker captures it. -
Strict Validation Behavior: The client’s
OpenIDConnectConfigurationRetrievercompares the authority oftoken_endpoint(evil.com) with the issuer (auth.example.com). They do not match. The client throws an exception and aborts the flow.
This distinction is vital. The issuer might be correct, but the endpoints are not. Strict discovery validation ensures that the entire trust chain remains within the authorized origin.
Conclusion
Implementing OAuth 2.1 discovery is not about fetching a JSON file. It is about enforcing a security policy that binds all operational endpoints to the verified issuer. By rigorously validating the issuer’s origin and ensuring all discovered endpoints share that same authority, you eliminate the risk of authorization server substitution and endpoint redirection attacks. Use libraries like OpenIDConnectConfigurationRetriever with their strict validation modes enabled, and never bypass the origin checks. This approach is fundamental to oauth 2.1 security best practices. In identity security, the discovery document is your map—if the map leads to a cliff, you must refuse to walk it.
Related posts
Decoding client_secret_basic Default in RFC 8414
An examination of the token_endpoint_auth_methods_supported metadata field in RFC 8414 and why client_secret_basic remains the default authentication method.
Understanding OAuth2 Authorization Server Metadata (RFC 8414)
An examination of RFC 8414 regarding OAuth2 authorization server metadata, covering discovery mechanisms and configuration endpoints for secure integration.
Securing gRPC with OAuth2 Token Propagation in Microservices
A guide to securing gRPC services using OAuth2 token propagation and interceptors for reliable microservice communication.