clockworklabs/SpacetimeDB · error · InvalidOperationException

JWT missing or invalid 'iss' claim

Error message

JWT missing or invalid 'iss' claim

What it means

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.

Source

Thrown at crates/bindings-csharp/Runtime/JwtClaims.cs:62

            }

            throw new InvalidOperationException("JWT missing or invalid 'sub' claim");
        }
    }

    public string Issuer
    {
        get
        {
            if (
                RootElement.TryGetProperty("iss", out var iss)
                && iss.ValueKind == JsonValueKind.String
            )
            {
                return iss.GetString()!;
            }

            throw new InvalidOperationException("JWT missing or invalid 'iss' claim");
        }
    }

    private List<string> ExtractAudience()
    {
        if (!RootElement.TryGetProperty("aud", out var aud))
        {
            return [];
        }

        return aud.ValueKind switch
        {
            JsonValueKind.String => [aud.GetString()!],
            JsonValueKind.Array =>
            [
                .. aud.EnumerateArray()
                    .Where(e => e.ValueKind == JsonValueKind.String)
                    .Select(e => e.GetString()!),

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Inspect JwtClaims.RawPayload to confirm whether 'iss' exists and what type it is
  2. Fix the token issuer to emit a string 'iss' claim identifying who minted the token
  3. Read 'iss' defensively via JsonDocument.Parse on RawPayload if your module tolerates its absence

Example fix

// before
var issuer = ctx.Auth.Jwt!.Issuer; // throws if 'iss' missing/non-string

// after
using var doc = JsonDocument.Parse(ctx.Auth.Jwt!.RawPayload);
var issuer = doc.RootElement.TryGetProperty("iss", out var iss) && iss.ValueKind == JsonValueKind.String
    ? iss.GetString()!
    : "<unknown-issuer>";
Defensive patterns

Strategy: validation

Validate before calling

bool HasStringIss(JwtClaims? jwt)
{
    if (jwt == null) return false;
    using var doc = JsonDocument.Parse(jwt.RawPayload);
    return doc.RootElement.TryGetProperty("iss", out var i) && i.ValueKind == JsonValueKind.String;
}

Type guard

static bool HasIssuer(JwtClaims? jwt) => jwt != null && HasStringIss(jwt);

Try / catch

try { var issuer = jwt.Issuer; }
catch (InvalidOperationException) { /* no string 'iss'; treat as untrusted/unknown issuer */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/af92890e02683dc3. Report an issue: GitHub.