{"record":{"id":"fd5b4b1d8a2aee7f","repo":"clockworklabs/SpacetimeDB","slug":"jwt-missing-or-invalid-sub-claim","errorCode":null,"errorMessage":"JWT missing or invalid 'sub' claim","messagePattern":"JWT missing or invalid 'sub' claim","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"crates/bindings-csharp/Runtime/JwtClaims.cs","lineNumber":46,"sourceCode":"    }\n\n    private JsonDocument Parsed => _parsed.Value;\n\n    private JsonElement RootElement => Parsed.RootElement;\n\n    public string Subject\n    {\n        get\n        {\n            if (\n                RootElement.TryGetProperty(\"sub\", out var sub)\n                && sub.ValueKind == JsonValueKind.String\n            )\n            {\n                return sub.GetString()!;\n            }\n\n            throw new InvalidOperationException(\"JWT missing or invalid 'sub' claim\");\n        }\n    }\n\n    public string Issuer\n    {\n        get\n        {\n            if (\n                RootElement.TryGetProperty(\"iss\", out var iss)\n                && iss.ValueKind == JsonValueKind.String\n            )\n            {\n                return iss.GetString()!;\n            }\n\n            throw new InvalidOperationException(\"JWT missing or invalid 'iss' claim\");\n        }\n    }","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-csharp/Runtime/JwtClaims.cs#L28-L64","documentation":"Thrown by the Subject accessor of JwtClaims, which wraps the decoded JWT payload of the token a connected client presented. In the C# server-module bindings the payload is fetched over FFI (AuthCtx.FromConnectionId -> FFI.get_jwt) and parsed lazily, so the error fires on first access of the property. The library assumes standards-compliant tokens where 'sub' is a string and fails fast rather than returning null.","triggerScenarios":"Reading authCtx.Jwt!.Subject (e.g. inspecting the sender identity in a reducer) when the client's JWT payload has no 'sub' claim, or 'sub' is not a JSON string (null, number, object, array).","commonSituations":"Custom or non-OIDC token issuers that omit standard claims; test tokens generated with default/empty payloads on jwt.io-style tools; accidentally treating the JWT header segment as the payload; tokens from an older or non-standard auth flow.","solutions":["Inspect the raw token payload via JwtClaims.RawPayload (it is the raw JSON string) to see exactly which claims the token carries","Fix the token issuer to include a string 'sub' claim (e.g. \"sub\": \"user-123\")","Use tokens from an OIDC-compliant provider or the SpacetimeDB auth stack so 'sub' is always a string","If 'sub' is legitimately optional for your module, read it defensively with JsonDocument.Parse on RawPayload instead of the Subject property"],"exampleFix":"// before\nvar senderId = ctx.Auth.Jwt!.Subject; // throws if 'sub' missing/non-string\n\n// after\nvar jwt = ctx.Auth.Jwt;\nvar senderId = jwt != null && TryGetClaim(jwt.RawPayload, \"sub\", out var s) ? s : \"<anonymous>\";","handlingStrategy":"validation","validationCode":"bool HasStringSub(JwtClaims? jwt)\n{\n    if (jwt == null) return false;\n    using var doc = JsonDocument.Parse(jwt.RawPayload);\n    return doc.RootElement.TryGetProperty(\"sub\", out var s) && s.ValueKind == JsonValueKind.String;\n}","typeGuard":"static bool HasSubject(JwtClaims? jwt) => jwt != null && HasStringSub(jwt);","tryCatchPattern":"try { var sub = jwt.Subject; }\ncatch (InvalidOperationException) { /* token lacks a string 'sub'; fall back to default identity handling */ }","preventionTips":["Check ctx.Auth.HasJwt and validate the payload shape before reading claim properties","Only accept tokens from issuers guaranteed to emit a string 'sub'","Log RawPayload (not the raw token) when claim errors occur to speed up diagnosis"],"tags":["jwt","claims","authentication","csharp"],"backgroundTag":"jwt-missing-claim","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}