{"record":{"id":"51f5938cb854193a","repo":"clockworklabs/SpacetimeDB","slug":"subject-empty","errorCode":null,"errorMessage":"Subject empty","messagePattern":"Subject empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/auth/src/identity.rs","lineNumber":113,"sourceCode":"}\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,\n            issuer: self.issuer,\n            audience: self.audience,\n            iat: self.iat,","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/auth/src/identity.rs#L95-L131","documentation":"The subject claim must be non-empty (crates/auth/src/identity.rs:113); together with the issuer it feeds Identity::from_claims, so a missing/empty sub aborts claim conversion with this error.","triggerScenarios":"Authenticating with a token where sub is absent or the empty string — e.g. client-credential/service tokens that identify only via other claims, or mis-minted tokens.","commonSituations":"Machine-to-machine tokens that omit sub; token-minting bugs; providers issuing anonymous tokens with an empty subject for unauthenticated flows.","solutions":["Include a non-empty sub (any stable identifier up to 128 bytes) in every token","For service identities, use a synthetic subject like 'service:my-service'","Correct minting code that leaves sub blank and re-issue tokens"],"exampleFix":"// before (JWT payload)\n{ \"iss\": \"https://identity.example.com\", \"sub\": \"\" }\n\n// after\n{ \"iss\": \"https://identity.example.com\", \"sub\": \"service:my-service\" }","handlingStrategy":"validation","validationCode":"// Reject tokens with empty/missing sub before authentication:\nconst claims = decodeJwtPayload(token);\nif (typeof claims.sub !== 'string' || claims.sub.length === 0) {\n  throw new Error('Token rejected: sub claim missing or empty');\n}","typeGuard":"function hasSubject(claims: Record<string, unknown>): claims is { sub: string } {\n  return typeof claims.sub === 'string' && claims.sub.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Give service/machine tokens a synthetic non-empty sub","Assert sub presence in token fixtures","Do not mint 'anonymous' tokens with empty subjects for SpacetimeDB auth"],"tags":["auth","jwt","identity","claims","validation","spacetimedb"],"backgroundTag":"jwt-subject-missing","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}