Skip to content

JSON Error When Decoding JWT with Nested Object in sub Claim #427

Open
@richardriman

Description

@richardriman

JSON Error When Decoding JWT with Nested Object in sub Claim

When decoding a JWT with a nested object in the sub claim, jsonwebtoken fails with the error Error::Json("expected ',' or '}'", line: 1, column: 8), despite the JSON payload being valid and manually deserializable.

Steps to Reproduce

  1. Use jsonwebtoken version 9.3.0 (also tested with 8.x).
  2. Define a claim struct with a nested object in sub:
    use serde::{Serialize, Deserialize};
    
    #[derive(Debug, Serialize, Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct SubClaims {
        user_id: i32,
        tenant_name: String,
        tenant_id: i32,
    }
    
    #[derive(Debug, Serialize, Deserialize)]
    struct Claims {
        sub: SubClaims,
        exp: usize,
        iss: String,
        aud: String,
    }
  3. Encode a token with HS256 (or HS512):
    use jsonwebtoken::{encode, Header, Algorithm, EncodingKey, Validation};
    
    let claims = Claims {
        sub: SubClaims {
            user_id: 105,
            tenant_name: "test".to_string(),
            tenant_id: 1,
        },
        exp: 10000000000,
        iss: "Issuer".to_string(),
        aud: "Audience".to_string(),
    };
    let secret = "SOME SECRET";
    let token = encode(
        &Header::new(Algorithm::HS512),
        &claims,
        &EncodingKey::from_secret(secret.as_ref()),
    ).unwrap();
  4. Attempt to decode:
    use jsonwebtoken::{decode, DecodingKey};
    
    let mut validation = Validation::new(Algorithm::HS512);
    validation.set_audience(&["Audience"]);
    validation.set_issuer(&["Issuer"]);
    let claims = decode::<Claims>(
        &token,
        &DecodingKey::from_secret(secret.as_ref()),
        &validation,
    );

Expected Behavior

The token should decode successfully into the Claims struct.

Actual Behavior

Decoding fails with Error::Json("expected ',' or '}'", line: 1, column: 8).

Additional Notes

  • Manual deserialization of the payload using serde_json::from_str and serde_json::from_slice works correctly:
    use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
    let parts: Vec<&str> = token.split('.').collect();
    let payload = parts[1];
    let decoded = URL_SAFE_NO_PAD.decode(payload).unwrap();
    let payload_str = String::from_utf8(decoded).unwrap();
    let claims = serde_json::from_str::<Claims>(&payload_str).unwrap(); // Works
    let claims = serde_json::from_slice::<Claims>(&decoded).unwrap(); // Works
  • Decoding works with a simple sub claim (e.g., sub: String):
    #[derive(Debug, Serialize, Deserialize)]
    struct SimpleClaims {
        sub: String,
        exp: usize,
        iss: String,
        aud: String,
    }
  • The issue persists with HS256, HS512, and older versions of jsonwebtoken (8.x).
  • The library uses URL_SAFE_NO_PAD for Base64 decoding (correct) and serde_json::from_slice for deserialization.
  • The raw bytes of the payload are valid UTF-8 and match the JSON:
    Payload: {"sub":{"userId":105,"tenantName":"info","tenantId":1},"exp":10000000000,"iss":"Issuer","aud":"Audience"}
    

Environment

  • Rust version: 1.82.0
  • jsonwebtoken: 9.3.0 (also tested with 8.x)
  • serde: 1.0.210
  • serde_json: 1.0.128
  • base64: 0.22

Possible Cause

The issue likely stems from jsonwebtoken passing an incorrect or truncated byte slice to serde_json::from_slice when deserializing a nested object in sub, or from a deserialization incompatibility with complex structures.

Request

Please investigate why jsonwebtoken::decode fails with nested objects in sub and provide a fix or clarification on whether complex sub claims are supported.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions