{"record":{"id":"3bb45ca15e9de468","repo":"xai-org/grok-build","slug":"audiencemismatch","errorCode":"AudienceMismatch","errorMessage":"OidcError::AudienceMismatch","messagePattern":"OidcError::AudienceMismatch","errorType":"error_code","errorClass":"OidcError","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs","lineNumber":689,"sourceCode":"        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,\n        principal_type: None,\n        principal_id: None,\n        team_id: None,\n        team_name: None,\n        team_role: None,","sourceCodeStart":671,"sourceCodeEnd":707,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs#L671-L707","documentation":"OidcError::AudienceMismatch is thrown when the ID token's `aud` claim does not contain the expected client_id (checked via aud_matches in validate_and_extract_user_info, protocol.rs:686-689). The `aud` claim identifies who the token was issued for; if this client isn't an audience, the token must not be accepted even if the signature is valid.","triggerScenarios":"extract_user_info called with expected_client_id C while the token's `aud` is a different client id (or an array not containing C). Note: when `aud` is absent the check is skipped; the error fires only on a present-but-mismatched aud.","commonSituations":"Client ID changed in config after the token was minted; using a token issued to another OAuth application; copy-pasted client_secret/client_id from a sibling app; multi-audience tokens where the expected client was dropped.","solutions":["Verify the client_id used to start the login flow matches the client_id passed to extract_user_info.","Decode the token payload and inspect `aud`; re-login with the correct OAuth client so the token lists your client_id.","Update the client configuration to the client_id the IdP actually issues tokens for."],"exampleFix":"// before: mismatched client id\nlet client_id = \"grok-shell-old\";\nlet user = extract_user_info(Some(&token), &discovery, &issuer, client_id, &nonce, ...).await?;\n// after: client id registered with the IdP for this app\nlet client_id = \"grok-shell\";","handlingStrategy":"validation","validationCode":"// pre-check aud claim\nfn token_aud(token: &str) -> Option<serde_json::Value> {\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()?.remove(\"aud\")\n}\nif let Some(aud) = token_aud(token) { /* verify expected_client_id appears in aud (string or array) */ }","typeGuard":"fn aud_contains(token: &str, client_id: &str) -> bool {\n    match token_aud(token) {\n        Some(serde_json::Value::String(s)) => s == client_id,\n        Some(serde_json::Value::Array(a)) => a.iter().any(|v| v.as_str() == Some(client_id)),\n        _ => true, // absent aud is not rejected by the library\n    }\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"AudienceMismatch\") => eprintln!(\"token aud does not include client_id; re-login with correct client\"),\n    other => other,\n}","preventionTips":["Keep client_id consistent between the authorize request and validation call.","After changing OAuth app registration, force users to re-authenticate.","Log aud claims when debugging multi-app setups."],"tags":["oidc","jwt","audience-validation","oauth"],"backgroundTag":"jwt-audience-mismatch","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}