zed-industries/zed · error

authorization server does not support S256 PKCE

Error message

authorization server does not support S256 PKCE

What it means

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.

Source

Thrown at crates/context_server/src/oauth.rs:874

pub async fn discover(
    http_client: &Arc<dyn HttpClient>,
    server_url: &Url,
    www_authenticate: &WwwAuthenticate,
) -> Result<OAuthDiscovery> {
    let resource_metadata =
        fetch_protected_resource_metadata(http_client, server_url, www_authenticate).await?;

    let auth_server_url = resource_metadata
        .authorization_servers
        .first()
        .ok_or_else(|| anyhow!("no authorization servers in resource metadata"))?;

    let auth_server_metadata = fetch_auth_server_metadata(http_client, auth_server_url).await?;

    // Verify PKCE S256 support (spec requirement).
    match &auth_server_metadata.code_challenge_methods_supported {
        Some(methods) if methods.iter().any(|m| m == "S256") => {}
        Some(_) => bail!("authorization server does not support S256 PKCE"),
        None => bail!("authorization server does not advertise code_challenge_methods_supported"),
    }

    let scopes = select_scopes(www_authenticate, &resource_metadata);

    Ok(OAuthDiscovery {
        resource_metadata,
        auth_server_metadata,
        scopes,
    })
}

/// Resolve the OAuth client registration for an authorization flow.
///
/// CIMD uses the static client metadata document directly. For DCR, a fresh
/// registration is performed each time because the loopback redirect URI
/// includes an ephemeral port that changes every flow.
pub async fn resolve_client_registration(

View on GitHub (pinned to f4178619ac)

Solutions

  1. 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)
  2. Fix the metadata value to advertise the exact string "S256" if the server actually supports it but lists it differently
  3. If the server genuinely cannot do S256, it is incompatible with MCP authorization — replace or front it with one that can

Example fix

// before (auth server metadata)
"code_challenge_methods_supported": ["plain"]

// after
"code_challenge_methods_supported": ["S256", "plain"]
Defensive patterns

Strategy: validation

Validate before calling

// client-side: inspect metadata before entering the flow
let methods = metadata.get("code_challenge_methods_supported")
    .and_then(|v| v.as_array())
    .map(|a| a.iter().filter_map(|m| m.as_str().to_string()).collect::<Vec<_>>());
anyhow::ensure!(
    methods.as_deref().is_some_and(|m| m.contains(&"S256".to_string())),
    "auth server must advertise S256 PKCE; got {:?}", methods
);

Type guard

fn supports_s256_pkce(doc: &serde_json::Value) -> bool {
    doc.get("code_challenge_methods_supported")
        .and_then(|v| v.as_array())
        .is_some_and(|list| list.iter().any(|m| m.as_str() == Some("S256")))
}

Try / catch

match discover(&client, &server_url, &challenge).await {
    Err(err) if err.to_string().contains("S256") => {
        // server PKCE config problem — enable S256 server-side; client retries cannot fix it
        report_server_requirement("enable and advertise code_challenge_methods_supported: [\"S256\"]");
        Err(err)
    }
    other => other,
}

Prevention

When it happens

Trigger: 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!.

Common situations: 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.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/e7b4dbe583f3ffaa. Report an issue: GitHub.