{"record":{"id":"ad44e6e3e0834946","repo":"clockworklabs/SpacetimeDB","slug":"issuer-empty","errorCode":null,"errorMessage":"Issuer empty","messagePattern":"Issuer empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/auth/src/identity.rs","lineNumber":110,"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":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/auth/src/identity.rs#L92-L128","documentation":"SpacetimeDB requires JWTs to carry a non-empty iss claim (crates/auth/src/identity.rs:110) because the identity is computed from iss and sub together; an empty issuer cannot derive a valid identity, so claim conversion fails immediately.","triggerScenarios":"Authenticating with a token where iss is absent (deserialization leaves it empty) or explicitly the empty string; tokens minted by test tooling that omits standard claims.","commonSituations":"Hand-rolled JWT minting that forgets iss; test/dev tokens signed with only sub; upstream proxy stripping the iss claim while rewriting tokens.","solutions":["Ensure the token includes a non-empty iss claim (at most 128 bytes)","Fix the token-minting code or provider config that drops iss","Regenerate test fixtures/tokens with the full standard claim set"],"exampleFix":"// before (JWT payload)\n{ \"sub\": \"12345\" }\n\n// after\n{ \"iss\": \"https://identity.example.com\", \"sub\": \"12345\" }","handlingStrategy":"validation","validationCode":"// Reject tokens with empty/missing iss before authentication:\nconst claims = decodeJwtPayload(token); // your decoder\nif (typeof claims.iss !== 'string' || claims.iss.length === 0) {\n  throw new Error('Token rejected: iss claim missing or empty');\n}","typeGuard":"function hasIssuer(claims: Record<string, unknown>): claims is { iss: string } {\n  return typeof claims.iss === 'string' && claims.iss.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Require the standard iss claim in token-minting templates","Validate decoded claims in tests, not just signatures","Avoid proxies that rewrite/drop claims"],"tags":["auth","jwt","identity","claims","validation","spacetimedb"],"backgroundTag":"jwt-issuer-missing","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}