{"record":{"id":"799b1ed8c5afba39","repo":"zed-industries/zed","slug":"auth-server-metadata-issuer-mismatch-expected","errorCode":null,"errorMessage":"Auth server metadata issuer mismatch: expected {}, got {}","messagePattern":"Auth server metadata issuer mismatch: expected (.+?), got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/context_server/src/oauth.rs","lineNumber":817,"sourceCode":"        server_url\n    )\n}\n\n/// Fetch Authorization Server Metadata, trying RFC 8414 and OIDC Discovery\n/// endpoints in the priority order specified by the MCP spec.\npub async fn fetch_auth_server_metadata(\n    http_client: &Arc<dyn HttpClient>,\n    issuer: &Url,\n) -> Result<AuthServerMetadata> {\n    let candidate_urls = auth_server_metadata_urls(issuer);\n\n    for url in &candidate_urls {\n        match fetch_json::<AuthServerMetadataResponse>(http_client, url).await {\n            Ok(response) => {\n                let reported_issuer = response.issuer.unwrap_or_else(|| issuer.clone());\n\n                if reported_issuer != *issuer {\n                    bail!(\n                        \"Auth server metadata issuer mismatch: expected {}, got {}\",\n                        issuer,\n                        reported_issuer\n                    );\n                }\n\n                return Ok(AuthServerMetadata {\n                    issuer: reported_issuer,\n                    grant_types_supported: response.grant_types_supported,\n                    authorization_endpoint: response\n                        .authorization_endpoint\n                        .ok_or_else(|| anyhow!(\"missing authorization_endpoint\"))?,\n                    token_endpoint: response\n                        .token_endpoint\n                        .ok_or_else(|| anyhow!(\"missing token_endpoint\"))?,\n                    registration_endpoint: response.registration_endpoint,\n                    scopes_supported: response.scopes_supported,\n                    code_challenge_methods_supported: response.code_challenge_methods_supported,","sourceCodeStart":799,"sourceCodeEnd":835,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/context_server/src/oauth.rs#L799-L835","documentation":"RFC 8414 requires that the issuer value inside an authorization server's metadata document exactly match the issuer used to construct the well-known URL. fetch_auth_server_metadata() enforces this: it takes the issuer field from the response (falling back to the requested issuer when the field is absent) and compares Url equality; any difference bails with both values. This stops a metadata document hosted at one URL from claiming to be a different authorization server, which would break token validation downstream.","triggerScenarios":"fetch_auth_server_metadata(http_client, issuer) succeeds in fetching the document, but response.issuer (when present) differs from the issuer URL used to build the candidate well-known URL. Trivial differences like a trailing slash, default-port inclusion (https://auth.example.com:443 vs https://auth.example.com), scheme http vs https, or a path suffix all fail Url equality.","commonSituations":"Server behind a proxy where the internal issuer is http://internal:9000 but the advertised one is https://auth.example.com; metadata generated with a trailing slash mismatch; OIDC providers that require the issuer to exactly match their configured base URL (Keycloak realm URL with/without trailing slash is a classic); authorization_servers entry in Protected Resource Metadata pointing at a redirecting alias rather than the canonical issuer.","solutions":["Make the issuer field in the authorization server metadata byte-identical to the URL listed in authorization_servers / used for discovery, including scheme, host, port, and path","Fix proxy forwarding so the served metadata reflects the public origin (X-Forwarded-Proto/Host honored by the auth server)","For Keycloak-like servers, use the canonical realm issuer URL (with the exact trailing-slash form the provider reports)","curl the well-known document and diff its issuer value against the URL you configured in the MCP server metadata"],"exampleFix":"// before\nauthorization_servers: [\"https://auth.example.com\"]\n// document: { \"issuer\": \"https://auth.example.com/\" }  // trailing slash\n\n// after\n// document: { \"issuer\": \"https://auth.example.com\" }","handlingStrategy":"validation","validationCode":"// after fetching the AS metadata document, compare issuers yourself to give a better message\nlet doc: serde_json::Value = fetch_json(&client, &candidate).await?;\nif let Some(reported) = doc.get(\"issuer\").and_then(|v| v.as_str()) {\n    anyhow::ensure!(\n        Url::parse(reported)? == *issuer,\n        \"server metadata issuer {reported} != configured {issuer}; fix server config\"\n    );\n}","typeGuard":"fn issuers_match(configured: &Url, doc: &serde_json::Value) -> bool {\n    doc.get(\"issuer\")\n        .and_then(|v| v.as_str())\n        .and_then(|s| Url::parse(s).ok())\n        .map(|u| u == *configured)\n        .unwrap_or(true) // absent field falls back to configured, matching the library\n}","tryCatchPattern":"match fetch_auth_server_metadata(&client, &issuer).await {\n    Err(err) if err.to_string().contains(\"issuer mismatch\") => {\n        // fix the issuer in authorization_servers OR in the AS metadata; do not blindly retry\n        show_config_hint(\"make metadata issuer exactly equal the authorization_servers entry\");\n        Err(err)\n    }\n    other => other,\n}","preventionTips":["In your metadata generator, derive the issuer field from the same constant used to build the well-known URL","Watch for trailing-slash and default-port differences — Url equality is exact","Honor X-Forwarded-Proto/Host in proxies so the served issuer reflects the public origin"],"tags":["oauth","mcp","issuer","rfc-8414","metadata"],"backgroundTag":"oauth-issuer-mismatch","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}