{"record":{"id":"73f23388e4149528","repo":"xai-org/grok-build","slug":"oidcerror-issuermismatch","errorCode":null,"errorMessage":"OidcError::IssuerMismatch","messagePattern":"OidcError::IssuerMismatch","errorType":"validation","errorClass":"OidcError","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs","lineNumber":684,"sourceCode":"        .ok_or_else(|| anyhow::Error::new(OidcError::JwkNotFound { kid: kid.clone() }))?;\n    let decoding_key = jsonwebtoken::DecodingKey::from_jwk(jwk)?;\n    let alg = header.alg;\n    ensure_alg_allowed(\n        alg,\n        discovery.id_token_signing_alg_values_supported.as_deref(),\n    )?;\n    let mut validation = jsonwebtoken::Validation::new(alg);\n    validation.set_issuer(&[expected_issuer]);\n    validation.set_audience(&[expected_client_id]);\n    validation.validate_exp = true;\n    validation.validate_aud = true;\n    validation.required_spec_claims = [\"sub\", \"iss\", \"aud\", \"exp\"]\n        .into_iter()\n        .map(ToOwned::to_owned)\n        .collect();\n    let token_data = jsonwebtoken::decode::<IdTokenClaims>(token, &decoding_key, &validation)?;\n    if token_data.claims.iss.as_deref() != Some(expected_issuer) {\n        return Err(anyhow::Error::new(OidcError::IssuerMismatch));\n    }\n    if let Some(ref aud) = token_data.claims.aud\n        && !aud_matches(aud, expected_client_id)\n    {\n        return Err(anyhow::Error::new(OidcError::AudienceMismatch));\n    }\n    if token_data.claims.nonce.as_deref() != Some(expected_nonce) {\n        return Err(anyhow::Error::new(OidcError::NonceMismatch));\n    }\n    Ok(OidcUserInfo {\n        user_id: token_data\n            .claims\n            .sub\n            .unwrap_or_else(|| \"unknown\".to_string()),\n        email: token_data.claims.email,\n        first_name: token_data.claims.first_name,\n        last_name: token_data.claims.last_name,\n        profile_image_asset_id: token_data.claims.picture,","sourceCodeStart":666,"sourceCodeEnd":702,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs#L666-L702","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: config issuer with trailing slash, token iss without\nlet expected_issuer = \"https://auth.example.com/\";\n// after: match the IdP's iss claim exactly\nlet expected_issuer = \"https://auth.example.com\";","handlingStrategy":"validation","validationCode":"// pre-check issuer claim before invoking the flow\nfn token_issuer(token: &str) -> Option<String> {\n    let payload = token.split('.').nth(1)?;\n    let bytes = base64_url::decode(payload).ok()?;\n    serde_json::from_slice::<serde_json::Value>(&bytes).ok()?.get(\"iss\")?.as_str().map(String::from)\n}\nassert_eq!(token_issuer(token).as_deref(), Some(expected_issuer));","typeGuard":"fn issuer_matches(token: &str, expected: &str) -> bool { token_issuer(token).as_deref() == Some(expected) }","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"IssuerMismatch\") => eprintln!(\"token issuer != configured issuer; check OIDC_ISSUER config\"),\n    other => other,\n}","preventionTips":["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."],"tags":["oidc","jwt","issuer-validation","config"],"backgroundTag":"jwt-issuer-mismatch","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}