{"record":{"id":"e7b4dbe583f3ffaa","repo":"zed-industries/zed","slug":"authorization-server-does-not-support-s256-pkce","errorCode":null,"errorMessage":"authorization server does not support S256 PKCE","messagePattern":"authorization server does not support S256 PKCE","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/context_server/src/oauth.rs","lineNumber":874,"sourceCode":"pub async fn discover(\n    http_client: &Arc<dyn HttpClient>,\n    server_url: &Url,\n    www_authenticate: &WwwAuthenticate,\n) -> Result<OAuthDiscovery> {\n    let resource_metadata =\n        fetch_protected_resource_metadata(http_client, server_url, www_authenticate).await?;\n\n    let auth_server_url = resource_metadata\n        .authorization_servers\n        .first()\n        .ok_or_else(|| anyhow!(\"no authorization servers in resource metadata\"))?;\n\n    let auth_server_metadata = fetch_auth_server_metadata(http_client, auth_server_url).await?;\n\n    // Verify PKCE S256 support (spec requirement).\n    match &auth_server_metadata.code_challenge_methods_supported {\n        Some(methods) if methods.iter().any(|m| m == \"S256\") => {}\n        Some(_) => bail!(\"authorization server does not support S256 PKCE\"),\n        None => bail!(\"authorization server does not advertise code_challenge_methods_supported\"),\n    }\n\n    let scopes = select_scopes(www_authenticate, &resource_metadata);\n\n    Ok(OAuthDiscovery {\n        resource_metadata,\n        auth_server_metadata,\n        scopes,\n    })\n}\n\n/// Resolve the OAuth client registration for an authorization flow.\n///\n/// CIMD uses the static client metadata document directly. For DCR, a fresh\n/// registration is performed each time because the loopback redirect URI\n/// includes an ephemeral port that changes every flow.\npub async fn resolve_client_registration(","sourceCodeStart":856,"sourceCodeEnd":892,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/context_server/src/oauth.rs#L856-L892","documentation":"The MCP authorization spec mandates PKCE with the S256 challenge method. During discover(), after fetching auth server metadata, the code checks code_challenge_methods_supported and requires the list to contain 'S256'. A list that is present but lacks S256 (e.g. only 'plain', or 'S-256' with a non-standard spelling) triggers this bail before any authorization URL is built. This is a deliberate interop gate: without S256 the flow would be non-compliant and weaker against authorization-code interception.","triggerScenarios":"Auth server metadata contains \"code_challenge_methods_supported\": [\"plain\"] (or any list without the exact string \"S256\"), and discover() reaches the match arm Some(_) => bail!.","commonSituations":"Legacy OAuth2 server that supports only the deprecated 'plain' PKCE method; homegrown auth server that never configured PKCE support; case/typo variations like \"s256\" or \"S-256\" that fail the exact m == \"S256\" comparison.","solutions":["Enable PKCE S256 in the authorization server configuration (essentially every modern OAuth library supports it; for custom servers, implement SHA-256 of the verifier per RFC 7636)","Fix the metadata value to advertise the exact string \"S256\" if the server actually supports it but lists it differently","If the server genuinely cannot do S256, it is incompatible with MCP authorization — replace or front it with one that can"],"exampleFix":"// before (auth server metadata)\n\"code_challenge_methods_supported\": [\"plain\"]\n\n// after\n\"code_challenge_methods_supported\": [\"S256\", \"plain\"]","handlingStrategy":"validation","validationCode":"// client-side: inspect metadata before entering the flow\nlet methods = metadata.get(\"code_challenge_methods_supported\")\n    .and_then(|v| v.as_array())\n    .map(|a| a.iter().filter_map(|m| m.as_str().to_string()).collect::<Vec<_>>());\nanyhow::ensure!(\n    methods.as_deref().is_some_and(|m| m.contains(&\"S256\".to_string())),\n    \"auth server must advertise S256 PKCE; got {:?}\", methods\n);","typeGuard":"fn supports_s256_pkce(doc: &serde_json::Value) -> bool {\n    doc.get(\"code_challenge_methods_supported\")\n        .and_then(|v| v.as_array())\n        .is_some_and(|list| list.iter().any(|m| m.as_str() == Some(\"S256\")))\n}","tryCatchPattern":"match discover(&client, &server_url, &challenge).await {\n    Err(err) if err.to_string().contains(\"S256\") => {\n        // server PKCE config problem — enable S256 server-side; client retries cannot fix it\n        report_server_requirement(\"enable and advertise code_challenge_methods_supported: [\\\"S256\\\"]\");\n        Err(err)\n    }\n    other => other,\n}","preventionTips":["Enable PKCE S256 in the authorization server and advertise the exact string \"S256\"","Beware spelling variants (\"s256\", \"S-256\") — the comparison is exact","Include a PKCE-methods assertion in your auth server's deployment config test"],"tags":["oauth","mcp","pkce","authorization","spec-compliance"],"backgroundTag":"oauth-pkce-s256-unsupported","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}