clockworklabs/SpacetimeDB · critical
Token 'sub' claim is not a string
Error message
Token 'sub' claim is not a string
What it means
Same accessor, next check: the claims contain a sub key but its JSON value is not a string (a number, array, boolean, or object). RFC 7519 requires sub to be a case-sensitive string, so the .expect on as_str() panics for non-conforming tokens.
Source
Thrown at crates/bindings/src/lib.rs:1939
Self {
payload: jwt,
parsed: OnceCell::new(),
audience: OnceCell::new(),
}
}
fn get_parsed(&self) -> &serde_json::Value {
self.parsed
.get_or_init(|| serde_json::from_str(&self.payload).expect("Failed to parse JWT payload"))
}
/// Returns the tokens subject, from the sub claim.
pub fn subject(&self) -> &str {
self.get_parsed()
.get("sub")
.expect("Missing 'sub' claim")
.as_str()
.expect("Token 'sub' claim is not a string")
}
/// Returns the issuer for these credentials, from the iss claim.
pub fn issuer(&self) -> &str {
self.get_parsed().get("iss").unwrap().as_str().unwrap()
}
fn extract_audience(&self) -> Vec<String> {
let Some(aud) = self.get_parsed().get("aud") else {
return Vec::new();
};
match aud {
serde_json::Value::String(s) => vec![s.clone()],
serde_json::Value::Array(arr) => arr.iter().filter_map(|v| v.as_str().map(String::from)).collect(),
_ => panic!("Unexpected type for 'aud' claim in JWT"),
}
}
View on GitHub (pinned to 524b4487d9)
Solutions
- Issue sub as a string per RFC 7519 (stringify numeric IDs).
- Validate token conformance upstream before callers rely on subject().
- Fix fixtures so sub is a JSON string.
Example fix
// before: numeric subject panics
serde_json::json!({"sub": 12345});
// after: string subject
serde_json::json!({"sub": "12345"}); Defensive patterns
Strategy: type-guard
Type guard
fn claims_sub_is_string(claims_json: &str) -> bool {
serde_json::from_str::<serde_json::Value>(claims_json)
.ok()
.and_then(|v| v.get("sub").map(|s| s.is_string()))
.unwrap_or(false)
} Try / catch
let outcome = std::panic::catch_unwind(std::AssertUnwindSafe(|| claims.subject().to_string()));
if outcome.is_err() {
// 'sub' exists but is not a JSON string: fix the issuer to stringify IDs per RFC 7519.
} Prevention
- Always issue sub as a string; stringify numeric user IDs.
- Validate issued tokens with a JSON-schema check that pins claim types.
- Keep fixtures type-correct: sub: "12345", not sub: 12345.
When it happens
Trigger: Tokens where sub is numeric (e.g. {"sub": 12345}) issued by a custom auth service; fixtures with an object or boolean in sub; claims converted from another format that kept the native type.
Common situations: Home-grown identity services using numeric user IDs; mis-serialized fixtures; claims copied from databases where IDs are integers.
Related errors
- Missing 'sub' claim
- Failed to parse JWT payload
- Issuer too long: {:?}
- Subject too long: {:?}
- Issuer empty
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/8fc825d4fb576e23.
Report an issue: GitHub.