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

  1. Confirm you are passing the JWT (three dot-separated base64url segments), not the hex identity.
  2. Strip whitespace, newlines, and surrounding quotes before passing the token.
  3. Mint a fresh token with `spacetime login` (or re-export SPACETIMEDB_SPACETIME_TOKEN) and retry.
  4. 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

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


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