Hmbown/CodeWhale · error

OIDC discovery issuer does not match the requested issuer

Error message

OIDC discovery issuer does not match the requested issuer

What it means

OIDC discovery succeeded, but the `issuer` claim in the discovery document does not match the issuer that was requested. This is the standard OIDC issuer-mixup defense: endpoints discovered from an issuer are only trusted if the document self-identifies with exactly that issuer (ignoring a trailing slash).

Solutions

  1. Compare the discovery document's issuer claim with your configured issuer and correct whichever is wrong.
  2. Account for scheme and path exactly (only a trailing '/' is forgiven).
  3. If a proxy rewrites the issuer, fix the proxy or configure the issuer the provider actually advertises.
  4. Confirm you are querying the correct tenant/realm's discovery endpoint.

Example fix

// before
issuer = "https://accounts.example.com"  // document says https://auth.example.com -> mismatch
// after
issuer = "https://auth.example.com"  // matches discovery issuer
Defensive patterns

Strategy: validation

Validate before calling

let doc: serde_json::Value = fetch_discovery(issuer)?;
let advertised = doc["issuer"].as_str().unwrap_or_default().trim_end_matches('/');
if advertised != issuer.trim_end_matches('/') {
    eprintln!("issuer mismatch: configured={issuer} advertised={advertised}");
}

Try / catch

match validate_discovered_issuer(doc.issuer.as_deref(), expected) {
    Ok(()) => {},
    Err(e) => return Err(anyhow!("discovery issuer mismatch; fix configured issuer: {e:#}")),
}

Prevention

When it happens

Trigger: validate_discovered_issuer comparing discovery.issuer to the expected issuer and finding a mismatch after trimming whitespace and trailing '/'. Also triggered when the document's issuer field is missing entirely (that yields the distinct "OIDC discovery missing issuer" context).

Common situations: Typo in the configured issuer; provider behind a proxy rewriting the issuer; pointing at a sibling tenant that serves a different issuer claim; http vs https discrepancy in the issuer URL.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/4a46bb0b3ea4aff6. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/oauth.rs:669

            })
            .transpose()?,
        token_endpoint: validate_discovered_oauth_endpoint(
            discovery.token_endpoint,
            "token_endpoint",
            issuer,
        )?,
    })
}

/// Validate that an OIDC discovery document's issuer matches the requested issuer.
fn validate_discovered_issuer(discovered: Option<String>, expected: &str) -> Result<()> {
    let discovered = discovered
        .as_deref()
        .map(str::trim)
        .filter(|issuer| !issuer.is_empty())
        .context("OIDC discovery missing issuer")?;
    if discovered.trim_end_matches('/') != expected.trim_end_matches('/') {
        bail!("OIDC discovery issuer does not match the requested issuer");
    }
    let _ = oauth_endpoint_url(expected).context("OIDC issuer is not a trusted URL")?;
    Ok(())
}

/// Validate one discovered endpoint against the issuer: https-or-http scheme,
/// no plaintext downgrade, no embedded credentials, same origin.
fn validate_discovered_oauth_endpoint(
    endpoint: Option<String>,
    field: &str,
    issuer: &str,
) -> Result<String> {
    let endpoint = endpoint
        .as_deref()
        .map(str::trim)
        .filter(|endpoint| !endpoint.is_empty())
        .with_context(|| format!("OIDC discovery missing {field}"))?;
    let parsed = reqwest::Url::parse(endpoint)

View on GitHub (pinned to 73e0f67d83)