{"record":{"id":"4a46bb0b3ea4aff6","repo":"Hmbown/CodeWhale","slug":"oidc-discovery-issuer-does-not-match-the-requested-issuer","errorCode":null,"errorMessage":"OIDC discovery issuer does not match the requested issuer","messagePattern":"OIDC discovery issuer does not match the requested issuer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":669,"sourceCode":"            })\n            .transpose()?,\n        token_endpoint: validate_discovered_oauth_endpoint(\n            discovery.token_endpoint,\n            \"token_endpoint\",\n            issuer,\n        )?,\n    })\n}\n\n/// Validate that an OIDC discovery document's issuer matches the requested issuer.\nfn validate_discovered_issuer(discovered: Option<String>, expected: &str) -> Result<()> {\n    let discovered = discovered\n        .as_deref()\n        .map(str::trim)\n        .filter(|issuer| !issuer.is_empty())\n        .context(\"OIDC discovery missing issuer\")?;\n    if discovered.trim_end_matches('/') != expected.trim_end_matches('/') {\n        bail!(\"OIDC discovery issuer does not match the requested issuer\");\n    }\n    let _ = oauth_endpoint_url(expected).context(\"OIDC issuer is not a trusted URL\")?;\n    Ok(())\n}\n\n/// Validate one discovered endpoint against the issuer: https-or-http scheme,\n/// no plaintext downgrade, no embedded credentials, same origin.\nfn validate_discovered_oauth_endpoint(\n    endpoint: Option<String>,\n    field: &str,\n    issuer: &str,\n) -> Result<String> {\n    let endpoint = endpoint\n        .as_deref()\n        .map(str::trim)\n        .filter(|endpoint| !endpoint.is_empty())\n        .with_context(|| format!(\"OIDC discovery missing {field}\"))?;\n    let parsed = reqwest::Url::parse(endpoint)","sourceCodeStart":651,"sourceCodeEnd":687,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L651-L687","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nissuer = \"https://accounts.example.com\"  // document says https://auth.example.com -> mismatch\n// after\nissuer = \"https://auth.example.com\"  // matches discovery issuer","handlingStrategy":"validation","validationCode":"let doc: serde_json::Value = fetch_discovery(issuer)?;\nlet advertised = doc[\"issuer\"].as_str().unwrap_or_default().trim_end_matches('/');\nif advertised != issuer.trim_end_matches('/') {\n    eprintln!(\"issuer mismatch: configured={issuer} advertised={advertised}\");\n}","typeGuard":null,"tryCatchPattern":"match validate_discovered_issuer(doc.issuer.as_deref(), expected) {\n    Ok(()) => {},\n    Err(e) => return Err(anyhow!(\"discovery issuer mismatch; fix configured issuer: {e:#}\")),\n}","preventionTips":["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."],"tags":["oauth","oidc","discovery","issuer","security"],"backgroundTag":"unexpected-response-shape","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}