xai-org/grok-build · error · OidcError
OidcError::StateMismatch
Error message
OidcError::StateMismatch
What it means
OidcError::StateMismatch is raised by validate_state when the `state` parameter received on the OIDC callback does not equal the state value generated at login start. State is a CSRF protection; a mismatch means the callback response cannot be trusted to belong to this login session.
Source
Thrown at crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs:593
pub(super) first_name: Option<String>,
#[serde(default, alias = "family_name")]
pub(super) last_name: Option<String>,
#[serde(default)]
pub(super) picture: Option<String>,
}
pub(super) fn aud_matches(aud: &serde_json::Value, expected: &str) -> bool {
match aud {
serde_json::Value::String(s) => s == expected,
serde_json::Value::Array(values) => values
.iter()
.any(|v| matches!(v, serde_json::Value::String(s) if s == expected)),
_ => false,
}
}
pub(super) fn validate_state(expected: &str, received: &str) -> anyhow::Result<()> {
if received != expected {
tracing::warn!(expected = %expected, received = %received, "OIDC: state mismatch");
return Err(anyhow::Error::new(OidcError::StateMismatch));
}
Ok(())
}
/// Explicit JWA name mapping — avoids coupling to `jsonwebtoken::Algorithm`'s `Debug` repr.
pub(super) fn alg_to_jwa_name(alg: jsonwebtoken::Algorithm) -> &'static str {
match alg {
jsonwebtoken::Algorithm::RS256 => "RS256",
jsonwebtoken::Algorithm::RS384 => "RS384",
jsonwebtoken::Algorithm::RS512 => "RS512",
jsonwebtoken::Algorithm::PS256 => "PS256",
jsonwebtoken::Algorithm::PS384 => "PS384",
jsonwebtoken::Algorithm::PS512 => "PS512",
jsonwebtoken::Algorithm::ES256 => "ES256",
jsonwebtoken::Algorithm::ES384 => "ES384",
jsonwebtoken::Algorithm::EdDSA => "EdDSA",
other => match other {
jsonwebtoken::Algorithm::HS256 => "HS256",
jsonwebtoken::Algorithm::HS384 => "HS384",View on GitHub (pinned to bc7f02eddd)
Solutions
- Restart the login flow cleanly and use only the most recent login session's URL
- Ensure no other login is running in parallel that could mix callbacks
- Do not reuse or hand-edit the authorize URL; let the flow generate a fresh state
Defensive patterns
Strategy: validation
Validate before calling
// client-side sanity: ensure the callback state matches what the flow generated
fn states_match(expected: &str, received: &str) -> bool {
!expected.is_empty() && constant_time_eq(expected.as_bytes(), received.as_bytes())
} Try / catch
match res {
Err(e) if matches!(e.downcast_ref::<OidcError>(), Some(OidcError::StateMismatch)) => {
eprintln!("CSRF state mismatch — restart the login and use the newest URL");
}
other => other?,
} Prevention
- Run only one login session at a time so callbacks cannot cross
- Always use the freshly printed authorize URL; never reuse an old one
- Don't relay the callback through services that truncate query parameters
When it happens
Trigger: run_login_flow_with_config receives a callback and calls validate_state(expected, received); any string inequality triggers StateMismatch.
Common situations: Multiple concurrent login sessions whose callbacks crossed, browser/race delivering a stale callback, replayed or forged redirect, truncation of the state in a URL-length-limited relay.
Related errors
- no OIDC auth entry found in {}. Run `grok login` first.
- auth entry has no refresh_token — cannot refresh expired tok
- auth entry has no oidc_issuer — cannot refresh expired token
- auth entry has no oidc_client_id — cannot refresh expired to
- OIDC expires_in out of range: {secs}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/180e7a2a6427a9a2.
Report an issue: GitHub.