clockworklabs/SpacetimeDB · error · InvalidOperationException
Unexpected type for 'aud' claim in JWT
Error message
Unexpected type for 'aud' claim in JWT
What it means
JwtClaims.ExtractAudience (backing the lazily-evaluated Audience property) accepts an 'aud' claim that is a JSON string or an array of strings; a missing 'aud' is fine (empty list). Any other JSON value kind — number, boolean, object, or null — throws InvalidOperationException when Audience is first accessed.
Source
Thrown at crates/bindings-csharp/Runtime/JwtClaims.cs:82
}
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()!),
],
_ => throw new InvalidOperationException("Unexpected type for 'aud' claim in JWT"),
};
}
public IReadOnlyList<string> Audience => _audience.Value;
// TODO: Should this be exposed as a JsonDocument, since that it in the stdlib?
public string RawPayload => _payload;
}
View on GitHub (pinned to 524b4487d9)
Solutions
- Inspect JwtClaims.RawPayload to see the actual JSON type of 'aud'
- Fix the issuer to emit 'aud' as a string or an array of strings (the only shapes the JWT spec allows)
- If you must tolerate other shapes, parse RawPayload yourself with JsonDocument instead of using Audience
Example fix
// before
var audiences = jwt.Audience; // throws for aud: 42 / null / {}
// after
using var doc = JsonDocument.Parse(jwt.RawPayload);
List<string> audiences = [];
if (doc.RootElement.TryGetProperty("aud", out var aud))
{
if (aud.ValueKind == JsonValueKind.String) audiences.Add(aud.GetString()!);
else if (aud.ValueKind == JsonValueKind.Array)
audiences.AddRange(aud.EnumerateArray().Where(e => e.ValueKind == JsonValueKind.String).Select(e => e.GetString()!));
} Defensive patterns
Strategy: validation
Validate before calling
bool HasValidAudShape(JwtClaims? jwt)
{
if (jwt == null) return true; // missing 'aud' is fine
using var doc = JsonDocument.Parse(jwt.RawPayload);
if (!doc.RootElement.TryGetProperty("aud", out var aud)) return true;
return aud.ValueKind is JsonValueKind.String or JsonValueKind.Array;
} Type guard
static bool AudienceIsSafe(JwtClaims? jwt) => HasValidAudShape(jwt);
Try / catch
try { var aud = jwt.Audience; }
catch (InvalidOperationException e) when (e.Message.Contains("aud")) { /* non-standard 'aud' claim; parse RawPayload yourself */ } Prevention
- Confirm your auth server emits 'aud' as string or array of strings
- Never read Audience on tokens from untrusted sources without a shape check
When it happens
Trigger: Reading jwt.Audience when the token's 'aud' claim is a number (e.g. a client_id configured as an int), a boolean, null, or a JSON object. Only triggered by the first access because the audience list is computed lazily.
Common situations: Custom auth servers that store audience as a numeric app id; mis-serialized tokens where 'aud' becomes null; issuers that emit aud as an object map of {client: scopes}.
Related errors
- JWT missing or invalid 'sub' claim
- JWT missing or invalid 'iss' claim
- Issuer too long: {:?}
- Subject too long: {:?}
- Issuer empty
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/5ab1d66fba4fb5cd.
Report an issue: GitHub.