{"record":{"id":"af92890e02683dc3","repo":"clockworklabs/SpacetimeDB","slug":"jwt-missing-or-invalid-iss-claim","errorCode":null,"errorMessage":"JWT missing or invalid 'iss' claim","messagePattern":"JWT missing or invalid 'iss' claim","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"crates/bindings-csharp/Runtime/JwtClaims.cs","lineNumber":62,"sourceCode":"            }\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    }\n\n    private List<string> ExtractAudience()\n    {\n        if (!RootElement.TryGetProperty(\"aud\", out var aud))\n        {\n            return [];\n        }\n\n        return aud.ValueKind switch\n        {\n            JsonValueKind.String => [aud.GetString()!],\n            JsonValueKind.Array =>\n            [\n                .. aud.EnumerateArray()\n                    .Where(e => e.ValueKind == JsonValueKind.String)\n                    .Select(e => e.GetString()!),","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-csharp/Runtime/JwtClaims.cs#L44-L80","documentation":"Thrown by the Issuer accessor of JwtClaims when the decoded JWT payload has no 'iss' claim or 'iss' is not a JSON string. Like Subject, parsing is lazy (Lazy<JsonDocument> over the payload string retrieved via FFI.get_jwt), so the exception occurs when the property is first read, not when the token arrives.","triggerScenarios":"Reading authCtx.Jwt!.Issuer on a token whose payload lacks 'iss' or carries a non-string 'iss' (null, number, object). Tokens minted without the issuer claim trigger this on every read.","commonSituations":"Hand-crafted dev/test tokens that only carry custom claims; custom identity providers that skip issuer; multi-tenant setups where you expected 'iss' to identify the tenant but the issuer never set it.","solutions":["Inspect JwtClaims.RawPayload to confirm whether 'iss' exists and what type it is","Fix the token issuer to emit a string 'iss' claim identifying who minted the token","Read 'iss' defensively via JsonDocument.Parse on RawPayload if your module tolerates its absence"],"exampleFix":"// before\nvar issuer = ctx.Auth.Jwt!.Issuer; // throws if 'iss' missing/non-string\n\n// after\nusing var doc = JsonDocument.Parse(ctx.Auth.Jwt!.RawPayload);\nvar issuer = doc.RootElement.TryGetProperty(\"iss\", out var iss) && iss.ValueKind == JsonValueKind.String\n    ? iss.GetString()!\n    : \"<unknown-issuer>\";","handlingStrategy":"validation","validationCode":"bool HasStringIss(JwtClaims? jwt)\n{\n    if (jwt == null) return false;\n    using var doc = JsonDocument.Parse(jwt.RawPayload);\n    return doc.RootElement.TryGetProperty(\"iss\", out var i) && i.ValueKind == JsonValueKind.String;\n}","typeGuard":"static bool HasIssuer(JwtClaims? jwt) => jwt != null && HasStringIss(jwt);","tryCatchPattern":"try { var issuer = jwt.Issuer; }\ncatch (InvalidOperationException) { /* no string 'iss'; treat as untrusted/unknown issuer */ }","preventionTips":["Verify issuer presence with a payload probe before reading Issuer in hot paths","Standardize on one token issuer so claim shape is predictable","Write a unit test asserting your issuer's tokens contain string 'iss' and 'sub'"],"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"}