{"record":{"id":"dd31490af168fbca","repo":"clockworklabs/SpacetimeDB","slug":"failed-to-parse-jwt-payload","errorCode":null,"errorMessage":"Failed to parse JWT payload","messagePattern":"Failed to parse JWT payload","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/bindings/src/lib.rs","lineNumber":1930,"sourceCode":"    ///\n    /// [JWT]: https://en.wikipedia.org/wiki/JSON_Web_Token\n    pub fn jwt(&self) -> Option<&JwtClaims> {\n        self.jwt.as_ref().deref().as_ref()\n    }\n}\n\nimpl JwtClaims {\n    fn new(jwt: String) -> Self {\n        Self {\n            payload: jwt,\n            parsed: OnceCell::new(),\n            audience: OnceCell::new(),\n        }\n    }\n\n    fn get_parsed(&self) -> &serde_json::Value {\n        self.parsed\n            .get_or_init(|| serde_json::from_str(&self.payload).expect(\"Failed to parse JWT payload\"))\n    }\n\n    /// Returns the tokens subject, from the sub claim.\n    pub fn subject(&self) -> &str {\n        self.get_parsed()\n            .get(\"sub\")\n            .expect(\"Missing 'sub' claim\")\n            .as_str()\n            .expect(\"Token 'sub' claim is not a string\")\n    }\n\n    /// Returns the issuer for these credentials, from the iss claim.\n    pub fn issuer(&self) -> &str {\n        self.get_parsed().get(\"iss\").unwrap().as_str().unwrap()\n    }\n\n    fn extract_audience(&self) -> Vec<String> {\n        let Some(aud) = self.get_parsed().get(\"aud\") else {","sourceCodeStart":1912,"sourceCodeEnd":1948,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings/src/lib.rs#L1912-L1948","documentation":"JwtClaims wraps the JWT payload string supplied by the host via ctx.jwt() - the verified claims document as JSON. get_parsed lazily parses it with serde_json, and this .expect panics if it is not valid JSON. In production the host always provides well-formed claims, so this mostly trips in unit tests using AuthCtx::from_jwt_payload with a hand-written string (e.g. a raw compact JWT or a base64 fragment).","triggerScenarios":"Unit tests calling AuthCtx::from_jwt_payload with a full header.payload.signature token or a base64-encoded blob instead of decoded claims JSON; a mocked host returning a non-JSON string; version skew in the claims format.","commonSituations":"Developers pasting an entire JWT into test fixtures; mocks returning the wrong string field; CI tests that never exercise jwt() locally.","solutions":["Pass the decoded claims object serialized as JSON, e.g. {\"sub\":\"user-1\",\"iss\":\"https://issuer\"}.","In production code use ctx.jwt()/has_jwt() rather than constructing claims manually.","Validate that test fixtures parse as JSON before wiring them into AuthCtx."],"exampleFix":"// before: raw compact JWT - the payload string is not JSON\nlet ctx = AuthCtx::from_jwt_payload(String::from(\"eyJhbGciOiJIUzI1NiJ9...\"));\n\n// after: decoded claims as JSON\nlet claims = serde_json::json!({\"sub\": \"user-1\", \"iss\": \"https://issuer.example\"}).to_string();\nlet ctx = AuthCtx::from_jwt_payload(claims);","handlingStrategy":"validation","validationCode":"fn is_valid_claims_json(s: &str) -> bool {\n    serde_json::from_str::<serde_json::Value>(s).is_ok()\n}\n\n// Use in test setup so bad fixtures fail loudly before reaching AuthCtx:\nassert!(is_valid_claims_json(&jwt_payload), \"fixture must be decoded claims JSON\");","typeGuard":null,"tryCatchPattern":"let outcome = std::panic::catch_unwind(std::AssertUnwindSafe(|| ctx.jwt().unwrap().subject().to_string()));\nif outcome.is_err() {\n    // Payload was not JSON: fix the fixture/issuer - the claims string handed to\n    // JwtClaims must be the decoded JSON claims document.\n}","preventionTips":["In tests, pass decoded claims JSON via serde_json::json!(...).to_string(), never a raw JWT.","In production, rely on ctx.jwt() from the host rather than constructing claims.","Check has_jwt() before touching claims."],"tags":["jwt","auth","panic","rust","spacetimedb"],"backgroundTag":"jwt-parse-error","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}