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
- Compare the discovery document's issuer claim with your configured issuer and correct whichever is wrong.
- Account for scheme and path exactly (only a trailing '/' is forgiven).
- If a proxy rewrites the issuer, fix the proxy or configure the issuer the provider actually advertises.
- 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
- Copy the issuer string exactly from the provider's own documentation or discovery document.
- Watch for proxies rewriting the issuer claim; align your config with what is actually advertised.
- Only a trailing '/' is forgiven — match scheme, host, and path exactly.
- Validate issuer config at startup with a discovery preflight.
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
- OIDC discovery returned unsupported
- OIDC discovery failed with HTTP
- OIDC discovery attempted to downgrade
- OIDC discovery returned credentials in
- OIDC discovery returned
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)