Skip to content
Ashish.
All posts
Diagram illustrating the OAuth 2.1 Discovery mechanism and issuer validation flow.
6 min readDevelopmentBackend Developers, Identity Engineers#oauth2#oauth2.1#openid-connect#discovery#security#issuer-validation#rfc8414#client-implementation

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.

By Ashish SrivastavaPart 6 of Authorization Server Metadata (RFC 8414)

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:

  1. Issuer Validation: Ensuring the iss claim matches the request origin.
  2. 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.com and https://example.com are different schemes.
  • http://example.com and http://example.com:80 are 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:

  1. Scheme Match: https must match https. Downgrade attacks from HTTP to HTTPS in the issuer claim must be rejected if the discovery endpoint was fetched over HTTPS.
  2. Host Match: The hostname must be identical.
  3. 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 True

This 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.com
  • token_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.Authority

If 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)

  1. Discovery Request: ClientApp requests https://auth.example.com/.well-known/openid-configuration.

  2. MITM Attack: Attacker intercepts 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"
    }
  3. Naive Client Behavior: The client parses the JSON. It sees issuer matches the request URL. It proceeds to use the token_endpoint.

  4. The Breach: The client sends the authorization code to https://evil.com/token. The Attacker captures it.

  5. Strict Validation Behavior: The client’s OpenIDConnectConfigurationRetriever compares the authority of token_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