{"record":{"id":"9f1e2bb77a809174","repo":"clockworklabs/SpacetimeDB","slug":"issuer-mismatch-got-expected","errorCode":null,"errorMessage":"Issuer mismatch: got {:?}, expected {:?}","messagePattern":"Issuer mismatch: got (.+?), expected (.+?)","errorType":"validation","errorClass":"TokenValidationError","httpStatus":null,"severity":"error","filePath":"crates/core/src/auth/token_validation.rs","lineNumber":196,"sourceCode":"        if SystemTime::now()\n            .duration_since(exp)\n            .is_ok_and(|elapsed| elapsed > Duration::from_secs(60))\n        {\n            return Err(JwtErrorKind::ExpiredSignature.into());\n        }\n    }\n    Ok(())\n}\n\n#[async_trait]\nimpl TokenValidator for BasicTokenValidator {\n    async fn validate_token(&self, token: &str) -> Result<SpacetimeIdentityClaims, TokenValidationError> {\n        // This validates everything but the issuer.\n        let claims = self.public_key.validate_token(token).await?;\n        if let Some(expected_issuer) = &self.issuer\n            && *claims.issuer != **expected_issuer\n        {\n            return Err(TokenValidationError::Other(anyhow::anyhow!(\n                \"Issuer mismatch: got {:?}, expected {:?}\",\n                claims.issuer,\n                expected_issuer\n            )));\n        }\n        Ok(claims)\n    }\n}\n\n// Validates tokens by looking up public keys and caching them.\npub struct CachingOidcTokenValidator {\n    cache: async_cache::AsyncCache<Arc<JwksValidator>, KeyFetcher>,\n}\n\nimpl CachingOidcTokenValidator {\n    pub fn new(refresh_duration: Duration, expiry: Option<Duration>) -> Self {\n        let cache = async_cache::Options::new(refresh_duration, KeyFetcher)\n            .with_expire(expiry)","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/3653d2ed498fddbd83b7062aa6bf8970e18635ec/crates/core/src/auth/token_validation.rs#L178-L214","documentation":"BasicTokenValidator validated the token's signature and claims, but the configured expected issuer does not equal the token's iss claim (byte-for-byte, both shown with Debug quoting). A cryptographically valid token is still rejected because it was minted by a different identity provider than the one this validator trusts.","triggerScenarios":"Self-hosted spacetimedb configured with an issuer (e.g. via --jwt-issuer or a JWKS whose issuer field) that differs from the iss claim in the presented token; using testnet/mainnet-issued tokens against a private server with its own issuer.","commonSituations":"Environment mismatch (token from one environment, server configured for another); OIDC provider changing its issuer URL (trailing slash added/removed); server configs copied between deployments without updating the issuer.","solutions":["Compare the two values printed in the error and make the server's configured issuer match the token's iss exactly, including any trailing slash","Re-login against the correctly-configured server so a matching token is issued","If you operate the OIDC provider, keep its issuer URL stable and identical on both sides"],"exampleFix":"# before\nspacetimedb start --jwt-issuer https://auth.example.com   # token iss is https://auth.example.com/\n\n# after\nspacetimedb start --jwt-issuer https://auth.example.com/  # byte-exact match","handlingStrategy":"validation","validationCode":"// Decode the iss claim without verification and compare before submitting the token\nlet iss = unverified_iss(token)?; // split '.', base64-decode segment 1, read \"iss\"\nif iss != *expected_issuer {\n    return Err(format!(\"token issuer {iss} does not match configured {expected_issuer}\"));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep issuer configuration identical across environments; template it from one source of truth","Watch for trailing-slash differences in issuer URLs when providers normalize them","After any provider migration, re-issue tokens rather than reusing old ones"],"tags":["auth","jwt","oidc","issuer","server-config"],"backgroundTag":"jwt-issuer-mismatch","analyzedSha":"3653d2ed498fddbd83b7062aa6bf8970e18635ec","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}