xai-org/grok-build · error · OidcError
OidcError::IssuerMismatch
Error message
OidcError::IssuerMismatch
What it means
OidcError::IssuerMismatch is thrown after jsonwebtoken::decode succeeds when the token's `iss` claim does not equal the expected_issuer passed to validate_and_extract_user_info. This is a defense-in-depth check on top of the library-level issuer validation, ensuring the ID token came from the identity provider the client configured.
Source
Thrown at crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs:684
.ok_or_else(|| anyhow::Error::new(OidcError::JwkNotFound { kid: kid.clone() }))?;
let decoding_key = jsonwebtoken::DecodingKey::from_jwk(jwk)?;
let alg = header.alg;
ensure_alg_allowed(
alg,
discovery.id_token_signing_alg_values_supported.as_deref(),
)?;
let mut validation = jsonwebtoken::Validation::new(alg);
validation.set_issuer(&[expected_issuer]);
validation.set_audience(&[expected_client_id]);
validation.validate_exp = true;
validation.validate_aud = true;
validation.required_spec_claims = ["sub", "iss", "aud", "exp"]
.into_iter()
.map(ToOwned::to_owned)
.collect();
let token_data = jsonwebtoken::decode::<IdTokenClaims>(token, &decoding_key, &validation)?;
if token_data.claims.iss.as_deref() != Some(expected_issuer) {
return Err(anyhow::Error::new(OidcError::IssuerMismatch));
}
if let Some(ref aud) = token_data.claims.aud
&& !aud_matches(aud, expected_client_id)
{
return Err(anyhow::Error::new(OidcError::AudienceMismatch));
}
if token_data.claims.nonce.as_deref() != Some(expected_nonce) {
return Err(anyhow::Error::new(OidcError::NonceMismatch));
}
Ok(OidcUserInfo {
user_id: token_data
.claims
.sub
.unwrap_or_else(|| "unknown".to_string()),
email: token_data.claims.email,
first_name: token_data.claims.first_name,
last_name: token_data.claims.last_name,
profile_image_asset_id: token_data.claims.picture,View on GitHub (pinned to bc7f02eddd)
Solutions
- Compare the token's `iss` claim (decode the JWT payload) with the configured expected_issuer and align them exactly.
- Fix the issuer URL in the OIDC config, matching byte-for-byte what the IdP puts in `iss` (watch trailing slashes and scheme).
- Re-authenticate against the correct environment/tenant so the token issuer matches configuration.
Example fix
// before: config issuer with trailing slash, token iss without let expected_issuer = "https://auth.example.com/"; // after: match the IdP's iss claim exactly let expected_issuer = "https://auth.example.com";
Defensive patterns
Strategy: validation
Validate before calling
// pre-check issuer claim before invoking the flow
fn token_issuer(token: &str) -> Option<String> {
let payload = token.split('.').nth(1)?;
let bytes = base64_url::decode(payload).ok()?;
serde_json::from_slice::<serde_json::Value>(&bytes).ok()?.get("iss")?.as_str().map(String::from)
}
assert_eq!(token_issuer(token).as_deref(), Some(expected_issuer)); Type guard
fn issuer_matches(token: &str, expected: &str) -> bool { token_issuer(token).as_deref() == Some(expected) } Try / catch
match result {
Err(e) if e.to_string().contains("IssuerMismatch") => eprintln!("token issuer != configured issuer; check OIDC_ISSUER config"),
other => other,
} Prevention
- Configure the issuer exactly as the IdP emits it (scheme, host, path, no trailing slash unless the IdP uses one).
- Keep one issuer value per environment and source it from discovery, not hardcoded strings.
- Decode tokens at debug time to inspect iss before wiring config.
When it happens
Trigger: Calling extract_user_info with expected_issuer X but a token whose `iss` claim is Y (protocol.rs:683-685). Happens when discovery/config issuer and the token's issuer diverge.
Common situations: Wrong issuer URL in client config (trailing-slash differences, http vs https, staging vs prod); multi-tenant IdP issuing tenant-specific issuers; environment variable pointing to a different realm.
Related errors
- no OIDC auth entry found in {}. Run `grok login` first.
- auth entry has no oidc_issuer — cannot refresh expired token
- auth entry has no oidc_client_id — cannot refresh expired to
- OidcError::ForceLoginNoPrincipalsAllowed
- OidcError::UnsupportedAlg
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/73f23388e4149528.
Report an issue: GitHub.