jwt-simple: JSON Web Tokens for Rust

repository·master·Indexed 43 days ago

https://github.com/jedisct1/rust-jwt-simple

A lightweight, non-opinionated Rust implementation of JSON Web Tokens (JWT) and JSON Web Encryption (JWE) version 0.13.1. Supports a wide range of symmetric (HMAC, BLAKE2B) and asymmetric (RSA, ECDSA, EdDSA, and Post-Quantum ML-DSA) signature algorithms, as well as JWE key management via RSA-OAEP, AES Key Wrap, and ECDH-ES.

Tokens
19.6K
Snippets
67
Records
88
Agent score
53%

What's inside jwt-simple

  1. Design philosophy and alternatives to JWT

    master

    The jwt-simple crate is designed to provide a safe, simple, and low-complexity interface for JWT, specifically addressing common pitfalls.

    Core Design Goals:

    • Simplicity: Easy to use for Rust beginners.
    • Safety: Automatically performs signature verification and claims validation to prevent misuse.
    • Minimalism: Low code complexity and minimal external dependencies.
    • Wasm-Ready: Works out of the box in WebAssembly environments (e.g., FaaS platforms).

    Note on JWT Security: The author notes that JWT is an imperfect design and recommends considering PASETO or Biscuit if you have control over both token creation and verification.

  2. Use JWE for encrypted tokens

    master

    JWE (JSON Web Encryption) provides confidentiality by encrypting the token content, ensuring sensitive claims are not visible to intermediaries.

    Security Note on Authentication: Decryption alone does not prove who created a token. The level of proof depends on the mode:

    • A128KW, A256KW: Proves the sender knows the shared secret.
    • RSA-OAEP, ECDH-ES+A128KW, ECDH-ES+A256KW: Does not prove who the sender is (anonymous encryption). Anyone with the public key can mint a token that decrypts successfully. To authenticate the sender in these modes, you must either use AES key wrap modes or require the sender to sign the data (e.g., by carrying a signed token in a custom claim).
  3. Mitigate replay attacks

    master

    Use these mechanisms to prevent replayed tokens:

    • Nonces: Use create_nonce() to add a nonce to a token, and use the required_nonce verification option to ensure it matches.
    • Expiration/Age limits: Reject tokens created too long ago using verification options.
    • Date thresholds: Reject tokens created before a specific date (e.g., the user's last successful login date).
  4. Using jwt-simple in Web browsers

    master
    While the wasm32-freestanding (or wasm32-unknown-unknown) target is supported and will compile, it is highly recommended to use a native JavaScript implementation instead. High-quality JavaScript JWT libraries that leverage the WebCrypto API provide superior performance and security guarantees compared to running the cryptography within a WebAssembly module.
  5. Authenticate using symmetric (HS*) algorithms

    master

    Symmetric authentication uses the same key for both creating and verifying tokens. This is suitable when both parties share a secret.

    Key and Token Creation

    1. Generate a key using HS256Key::generate() (or other HS* variants).
    2. Create claims using Claims::create(duration).
    3. Authenticate the claims using key.authenticate(claims).

    Keys can be exported as bytes via key.to_bytes() and restored via HS256Key::from_bytes().

    Token Verification

    Verify a token using key.verify_token::<ClaimsType>(&token, options). By default, expiration, start time, and authentication tags are automatically verified.

    use jwt_simple::prelude::*;
    
    // 1. Key creation
    let key = HS256Key::generate();
    
    // 2. Token creation
    let claims = Claims::create(Duration::from_hours(2));
    let token = key.authenticate(claims)?;
    
    // 3. Token verification
    let claims = key.verify_token::<NoCustomClaims>(&token, None)?;
  6. Sign and verify using asymmetric (Key Pair) algorithms

    master

    Asymmetric signatures use a secret key pair to sign tokens and a public key to verify them. This is recommended when the verifier should not be able to create tokens.

    Key Pair Generation

    • ECDSA: Use ES256KeyPair::generate() or ES384KeyPair::generate().
    • ML-DSA (Post-Quantum): Use MLDSA44KeyPair::generate() (recommended), MLDSA65KeyPair::generate(), or MLDSA87KeyPair::generate().
    • RSA: Keys can be imported from PEM-encoded data using RS384KeyPair::from_pem(content) and RS384PublicKey::from_pem(content).

    Usage Pattern

    1. Sign: Use the secret key pair with key_pair.sign(claims).
    2. Verify: Use the public key with public_key.verify_token::<ClaimsType>(&token, options).
    use jwt_simple::prelude::*;
    
    // 1. Create key pair (e.g., ES256)
    let key_pair = ES256KeyPair::generate();
    let public_key = key_pair.public_key();
    
    // 2. Sign token
    let claims = Claims::create(Duration::from_hours(2));
    let token = key_pair.sign(claims)?;
    
    // 3. Verify token with public key
    let claims = public_key.verify_token::<NoCustomClaims>(&token, None)?;
  7. Configure pure-rust implementation

    master

    If you encounter portability issues with the boring crate, you can compile using only Rust implementations.

    Note: This is considered unsupported and should only be used for specific targets/setups.

    In your Cargo.toml:

    [dependencies]
    jwt-simple = { version = "0.13.0", default-features = false, features = ["pure-rust"] }
  8. Enable WASI-Crypto for faster and safer WebAssembly cryptography

    master

    When targeting wasm32-wasip1, you can enable the wasi-crypto feature to offload heavy cryptographic operations (like RSA key generation, signing, verification, and AES-GCM encryption) to the host. This provides significant performance improvements and better protection against side-channel attacks by using the host's optimized, constant-time implementations.

    Key behaviors:

    • Target Specificity: The feature only has an effect on wasm32-wasip1. On all other targets, it is a no-op and safe to leave enabled.
    • Fallback Mechanism: If the host does not provide the WASI-Crypto interface, the library transparently falls back to pure Rust implementations without runtime errors.
    • Offloaded Operations: RSA (RS*, PS*) and AES-GCM (used in JWE tokens).
    [dependencies]
    jwt-simple = { version = "0.13", features = ["wasi-crypto"] }
    cargo build --target wasm32-wasip1 --features wasi-crypto
  9. Use custom claims in JWT

    master

    You can include application-defined data in claims by using a serializable type (requires the serde crate).

    #[derive(Serialize, Deserialize)]
    struct MyAdditionalData {
       user_is_admin: bool,
       user_country: String,
    }
    
    let my_additional_data = MyAdditionalData {
       user_is_admin: false,
       user_country: "FR".to_string(),
    };
    
    // Claim creation with custom data
    let claims = Claims::with_custom_claims(my_additional_data, Duration::from_secs(30));
    
    // Claim verification with custom data
    let claims = public_key.verify_token::<MyAdditionalData>(&token, None)?;
    let user_is_admin = claims.custom.user_is_admin;
  10. Use salted keys for symmetric signatures

    master

    Symmetric keys (like HS256) are vulnerable if the verifier's key is compromised. Salted keys mitigate this by using a secret 'signer salt' known only to the signer. The verifier uses a derived 'verifier salt' to check the token.

    1. Generate a key with a salt: HS256Key::generate_with_salt().
    2. The signer uses the full key. The verifier uses key.attach_salt(verifier_salt)? where the verifier_salt is derived from the signer's salt.
    // Create a random key and a signer salt
    let key = HS256Key::generate_with_salt();
    let claims = Claims::create(Duration::from_secs(86400));
    let token = key.authenticate(claims).unwrap();
    
    // --- On the Verifier side ---
    // 1. Get the verifier salt from the signer's salt
    let verifier_salt_bytes = key.verifier_salt()?; 
    let verifier_salt = Salt::Verifier(verifier_salt_bytes);
    
    // 2. Attach it to the key before verification
    key.attach_salt(verifier_salt)?;
    let claims = key.verify_token::<NoCustomClaims>(&token, None)?;
  11. Use salt for symmetric key authentication

    master

    For symmetric algorithms like HS256Key, you can use salts to enhance security. You can generate a key with a salt using generate_with_salt(), retrieve the verifier salt using verifier_salt(), and then attach it to a key instance using attach_salt(Salt::Verifier(vec)) to enable successful verification of tokens created with that salt.

    let mut key = HS256Key::generate_with_salt();
    let claims = Claims::create(Duration::from_secs(86400));
    let token = key.authenticate(claims).unwrap();
    
    // To verify, you must attach the salt used during authentication
    let verifier_salt = key.verifier_salt().unwrap();
    key.attach_salt(verifier_salt).unwrap();
    key.verify_token::<NoCustomClaims>(&token, None).unwrap();