{"record":{"id":"46b3daf4dc9392c9","repo":"clockworklabs/SpacetimeDB","slug":"identity-mismatch-token-identity-token-identity","errorCode":null,"errorMessage":"Identity mismatch: token identity {token_identity:?} does not match computed identity {computed_identity:?}","messagePattern":"Identity mismatch: token identity (.+?) does not match computed identity (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/auth/src/identity.rs","lineNumber":121,"sourceCode":"            return Err(anyhow::anyhow!(\"Issuer too long: {:?}\", self.issuer));\n        }\n        if self.subject.len() > 128 {\n            return Err(anyhow::anyhow!(\"Subject too long: {:?}\", self.subject));\n        }\n        // The issuer and subject must be non-empty.\n        if self.issuer.is_empty() {\n            return Err(anyhow::anyhow!(\"Issuer empty\"));\n        }\n        if self.subject.is_empty() {\n            return Err(anyhow::anyhow!(\"Subject empty\"));\n        }\n\n        let computed_identity = Identity::from_claims(&self.issuer, &self.subject);\n        // If an identity is provided, it must match the computed identity.\n        if let Some(token_identity) = self.identity\n            && token_identity != computed_identity\n        {\n            return Err(anyhow::anyhow!(\n                    \"Identity mismatch: token identity {token_identity:?} does not match computed identity {computed_identity:?}\",\n                ));\n        }\n\n        Ok(SpacetimeIdentityClaims {\n            identity: computed_identity,\n            subject: self.subject,\n            issuer: self.issuer,\n            audience: self.audience,\n            iat: self.iat,\n            exp: self.exp,\n            extra: self.extra,\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/auth/src/identity.rs#L103-L139","documentation":"SpacetimeDB computes the caller identity as Identity::from_claims(iss, sub). If the token additionally carries an explicit identity claim, it must equal that computed value (crates/auth/src/identity.rs:121); otherwise conversion fails. This stops tokens from asserting an identity other than the one their iss/sub hash to.","triggerScenarios":"Minting a token with an identity claim copied from an old iss/sub pair, then changing the issuer or subject (URL change, provider migration, casing/format tweak changes the hash) without recomputing identity; hand-assembled tokens with a stale identity field.","commonSituations":"Re-issuing tokens under a new issuer URL while reusing the old identity claim; token tooling that hardcodes an identity value; provider changing subject formatting after an upgrade.","solutions":["Drop the identity claim from the token — it is optional and SpacetimeDB derives it from iss/sub","Or recompute it: Identity::from_claims(current_iss, current_sub), and embed exactly that value","After any iss/sub change, treat the result as a new identity and re-grant subscriptions/permissions to it"],"exampleFix":"// before (JWT payload)\n{ \"iss\": \"https://new-issuer.example.com\", \"sub\": \"12345\", \"identity\": \"<hash-of-old-issuer+sub>\" }\n\n// after\n{ \"iss\": \"https://new-issuer.example.com\", \"sub\": \"12345\" }","handlingStrategy":"validation","validationCode":"// If you include an identity claim, verify it matches iss/sub before use:\n// (identity = Identity::from_claims(iss, sub) server-side; keep them in sync or omit it)\nfunction sanitizeClaims(claims: Record<string, unknown>) {\n  const { identity, ...rest } = claims;\n  if (identity !== undefined) {\n    console.warn('Dropping stale identity claim; SpacetimeDB derives it from iss/sub');\n  }\n  return rest;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Simplest rule: never embed an identity claim; let SpacetimeDB derive it","If you change issuer or subject format, treat it as a brand-new identity and re-grant access","Never copy identity values between tokens minted under different iss/sub"],"tags":["auth","jwt","identity","claims","validation","spacetimedb"],"backgroundTag":"jwt-identity-mismatch","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}