{"record":{"id":"51f5938cb854193a","repo":"clockworklabs/SpacetimeDB","slug":"subject-empty","errorCode":null,"errorMessage":"Subject empty","messagePattern":"Subject empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/auth/src/identity.rs","lineNumber":118,"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":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/3653d2ed498fddbd83b7062aa6bf8970e18635ec/crates/auth/src/identity.rs#L100-L136","documentation":"The claims conversion rejects an empty `sub` (crates/auth/src/identity.rs:113). The subject is the per-user half of the Identity hash input (Identity::from_claims(issuer, subject)); an empty subject cannot identify a principal, so the conversion fails before identity computation or the optional identity-match check at lines 117-123.","triggerScenarios":"Authenticating with a JWT whose `sub` claim is missing or empty - e.g. client-credentials/machine tokens from providers that omit sub, or tokens minted with the subject field left blank.","commonSituations":"Service-to-service tokens from a generic OIDC provider; custom token services that authenticated the caller out-of-band and forgot to set sub; malformed tokens hand-assembled for testing.","solutions":["Require the token service to populate `sub` with the calling principal's id (any non-empty value up to 128 bytes).","For machine identities, assign a stable synthetic subject (e.g. `svc-billing`) rather than leaving sub empty.","Reissue tokens after fixing issuance."],"exampleFix":"// before\n{ \"iss\": \"https://app.example.com\", \"sub\": \"\" }\n// after\n{ \"iss\": \"https://app.example.com\", \"sub\": \"svc-billing\" }","handlingStrategy":"validation","validationCode":"// Rust: require a non-empty subject before attempting the conversion.\nanyhow::ensure!(!claims.subject.is_empty(), \"token missing `sub` claim; refusing to derive identity\");","typeGuard":"fn has_subject(c: &IncomingClaims) -> bool {\n    !c.subject.is_empty()\n}","tryCatchPattern":null,"preventionTips":["Map every caller (human or service) to a stable non-empty subject id at token issuance.","For machine-to-machine auth, mint synthetic subjects like `svc-<name>` instead of leaving sub blank.","Add contract tests against your IdP's token format so claims regressions surface before production."],"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"}