clockworklabs/SpacetimeDB · error · anyhow::Error
Token does not look like a JSON web token: {token}
Error message
Token does not look like a JSON web token: {token} What it means
decode_identity in the CLI manually splits a JWT on '.' expecting exactly three segments (header.payload.signature), then base64-decodes the payload to read identity claims without verifying the signature. Anything that is not a three-part JWT — a hex identity string, a truncated token, or a token with extra characters — is rejected before decoding.
Source
Thrown at crates/cli/src/util.rs:338
if force {
println!("Skipping confirmation due to --yes");
return Ok(true);
}
let mut input = String::new();
print!("{prompt} [y/N]");
std::io::stdout().flush()?;
std::io::stdin().read_line(&mut input)?;
let input = input.trim().to_lowercase();
Ok(input == "y" || input == "yes")
}
pub fn decode_identity(token: &String) -> anyhow::Result<String> {
// Here, we manually extract and decode the claims from the json web token.
// We do this without using the `jsonwebtoken` crate because it doesn't seem to have a way to skip signature verification.
// But signature verification would require getting the public key from a server, and we don't necessarily want to do that.
let token_parts: Vec<_> = token.split('.').collect();
if token_parts.len() != 3 {
return Err(anyhow::anyhow!("Token does not look like a JSON web token: {token}"));
}
let decoded_bytes = BASE_64_STD_NO_PAD.decode(token_parts[1])?;
let decoded_string = String::from_utf8(decoded_bytes)?;
let claims_data: IncomingClaims = serde_json::from_str(decoded_string.as_str())?;
let claims_data: SpacetimeIdentityClaims = claims_data.try_into()?;
Ok(claims_data.identity.to_string())
}
pub async fn get_login_token_or_log_in(
config: &mut Config,
target_server: Option<&str>,
interactive: bool,
) -> anyhow::Result<String> {
if let Some(token) = config.spacetimedb_token() {
return Ok(token.clone());
}View on GitHub (pinned to 524b4487d9)
Solutions
- Confirm you are passing the JWT (three dot-separated base64url segments), not the hex identity.
- Strip whitespace, newlines, and surrounding quotes before passing the token.
- Mint a fresh token with `spacetime login` (or re-export SPACETIMEDB_SPACETIME_TOKEN) and retry.
- If the token came from a file, check it was not truncated or JSON-escaped.
Example fix
# before — hex identity passed by mistake spacetime identity decode 37101e97b3f2... # after — pass the JWT produced by `spacetime login` spacetime identity decode eyJhbGciOi....eyJ....SIG
Defensive patterns
Strategy: validation
Validate before calling
function assertJwtShape(token: string): void {
const parts = token.trim().split('.');
if (parts.length !== 3 || parts.some(p => p.length === 0)) {
throw new Error('expected a three-segment JWT (header.payload.signature), not an identity hex string');
}
} Type guard
function isJwtLike(token: string): boolean {
const t = token.trim();
return t.split('.').length === 3 && /^[A-Za-z0-9_-]+$/.test(t.split('.')[1]);
} Prevention
- Trim whitespace, newlines, and quotes from tokens before use.
- Store the JWT and the hex identity under clearly different variable names.
- Regenerate tokens with `spacetime login` rather than hand-editing them.
When it happens
Trigger: Passing a 32-byte hex Identity instead of the JWT to `spacetime identity decode` or the login flow; a token string that includes surrounding quotes, whitespace, or a trailing newline; a mangled or empty token read from a config file or env var.
Common situations: Confusing the public identity (hex) with the auth token (JWT); scripts reading the wrong line of a token file; copy-pasting a token from JSON where it was escaped or truncated.
Related errors
- Issuer too long: {:?}
- Subject too long: {:?}
- Issuer empty
- Subject empty
- Identity mismatch: token identity {token_identity:?} does no
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/1f442786a9228508.
Report an issue: GitHub.