{"record":{"id":"ad44e6e3e0834946","repo":"clockworklabs/SpacetimeDB","slug":"issuer-empty","errorCode":null,"errorMessage":"Issuer empty","messagePattern":"Issuer empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/auth/src/identity.rs","lineNumber":115,"sourceCode":"    /// All remaining claims from the JWT payload\n    #[serde(flatten)]\n    pub extra: Option<HashMap<Box<str>, serde_json::Value>>,\n}\n\nimpl TryInto<SpacetimeIdentityClaims> for IncomingClaims {\n    type Error = anyhow::Error;\n\n    fn try_into(self) -> anyhow::Result<SpacetimeIdentityClaims> {\n        // The issuer and subject must be less than 128 bytes.\n        if self.issuer.len() > 128 {\n            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,","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/3653d2ed498fddbd83b7062aa6bf8970e18635ec/crates/auth/src/identity.rs#L97-L133","documentation":"During IncomingClaims -> SpacetimeIdentityClaims conversion (crates/auth/src/identity.rs:110), an `iss` claim that deserializes to an empty string is rejected. The issuer is one of the two inputs to Identity::from_claims, so an empty issuer would collapse many tokens onto ambiguous identities. This fires when iss is present but empty, or absent and defaulted to empty.","triggerScenarios":"Authenticating with a JWT that omits the `iss` claim entirely, or carries `iss\": \"\"`; tokens minted by custom services that only set `sub`; test/self-signed tokens generated without an issuer field.","commonSituations":"Hand-rolled token generation that never set iss; providers configured to strip iss; sending a raw API key or opaque token where a JWT is expected, so claims parse to defaults.","solutions":["Ensure the token service always includes a non-empty `iss` claim (short canonical issuer id) in every issued JWT.","If the token is not a JWT with proper claims, use SpacetimeDB's own token flow instead of passing it through this claims path.","Regenerate and redistribute tokens after fixing issuance."],"exampleFix":"// before: payload missing issuer\n{ \"sub\": \"user-1\" }\n// after\n{ \"iss\": \"https://app.example.com\", \"sub\": \"user-1\" }","handlingStrategy":"validation","validationCode":"// Rust: require a non-empty issuer before attempting the conversion.\nanyhow::ensure!(!claims.issuer.is_empty(), \"token missing `iss` claim; refusing to derive identity\");","typeGuard":"fn has_issuer(c: &IncomingClaims) -> bool {\n    !c.issuer.is_empty()\n}","tryCatchPattern":null,"preventionTips":["Configure the token service to always include iss; reject iss-less tokens at your ingress.","When self-signing test tokens, start from a template that includes iss and sub.","Distinguish JWT traffic from raw API keys at the gateway so non-JWT tokens never reach claims validation."],"tags":["spacetimedb","auth","jwt","identity","missing-claim"],"backgroundTag":"jwt-claims-validation","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"}