#jwt #token #authorization #algorithm #minimalist #claim #primitive

jwt-compact-preview

Minimalistic JWT implementation with focus on type safety and secure cryptographic primitives

9 releases

0.4.0 Apr 29, 2021
0.3.8 Aug 28, 2020
0.3.6 Jul 25, 2020
0.3.4 Jun 29, 2020

#2440 in Cryptography

Apache-2.0

71KB
1.5K SLoC

Compact JWT implementation in Rust [archived]

Superseded by jwt-simple.


lib.rs:

Minimalistic JSON web token (JWT) implementation with focus on type safety and secure cryptographic primitives.

Design choices

  • JWT signature algorithms (i.e., cryptographic algorithms providing JWT integrity) are expressed via the Algorithm trait, which uses fully typed keys and signatures.
  • JWT header is represented by the Header struct. Notably, Header does not expose the alg field. Instead, alg is filled automatically during token creation, and is compared to the expected value during verification. (If you do not know the JWT signature algorithm during verification, you're doing something wrong.) This eliminates the possibility of algorithm switching attacks.

Additional features

  • The crate supports more compact CBOR encoding of the claims. The compactly encoded JWTs have cty field (content type) in their header set to "CBOR".
  • The crate supports EdDSA algorithm with the Ed25519 elliptic curve, and ES256K algorithm with the secp256k1 elliptic curve.

Supported algorithms

Algorithm(s) Feature Description
HS256, HS384, HS512 - Uses pure Rust sha2 crate
EdDSA (Ed25519) exonum-crypto libsodium binding. Enabled by default
EdDSA (Ed25519) ed25519-dalek Pure Rust implementation
EdDSA (Ed25519) ed25519-compact Compact pure Rust implementation, WASM-compatible
ES256K secp256k1 Binding for libsecp256k1
RS*, PS* (RSA) [rsa] Uses pure Rust [rsa] crate with blinding

StandardES* algorithm is not (yet?) implemented. The main reason (besides laziness and non-friendly APIs in the relevant crypto backends) is:

  • Elliptic curves in ES* algs use a maybe-something-up-my-sleeve generation procedure and thus may be backdoored

EdDSA and ES256K algorithms are non-standard. They both work with elliptic curves (Curve25519 and secp256k1; both are widely used in crypto community and believed to be securely generated). These algs have 128-bit security, making them an alternative to ES256.

Examples

Basic JWT lifecycle:

use chrono::{Duration, Utc};
use jwt_compact::{prelude::*, alg::{Hs256, Hs256Key}};
use serde::{Serialize, Deserialize};
use std::convert::TryFrom;

/// Custom claims encoded in the token.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct CustomClaims {
    /// `sub` is a standard claim which denotes claim subject:
    /// https://tools.ietf.org/html/rfc7519#section-4.1.2
    #[serde(rename = "sub")]
    subject: String,
}

// Create a symmetric HMAC key, which will be used both to create and verify tokens.
let key = Hs256Key::from(b"super_secret_key_donut_steel" as &[_]);
// Create a token.
let header = Header {
    key_id: Some("my-key".to_owned()),
    ..Default::default()
};
let claims = Claims::new(CustomClaims { subject: "alice".to_owned() })
    .set_duration_and_issuance(Duration::days(7))
    .set_not_before(Utc::now() - Duration::hours(1));
let token_string = Hs256.token(header, &claims, &key)?;
println!("token: {}", token_string);

// Parse the token.
let token = UntrustedToken::try_from(token_string.as_str())?;
// Before verifying the token, we might find the key which has signed the token
// using the `Header.key_id` field.
assert_eq!(token.header().key_id, Some("my-key".to_owned()));
// Validate the token integrity.
let token: Token<CustomClaims> = Hs256.validate_integrity(&token, &key)?;
// Validate additional conditions.
token
    .claims()
    .validate_expiration(TimeOptions::default())?
    .validate_maturity(TimeOptions::default())?;
// Now, we can extract information from the token (e.g., its subject).
let subject = &token.claims().custom.subject;
assert_eq!(subject, "alice");

Compact JWT

/// Custom claims encoded in the token.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct CustomClaims {
    /// `sub` is a standard claim which denotes claim subject:
    ///     https://tools.ietf.org/html/rfc7519#section-4.1.2
    /// The custom serializer we use allows to efficiently
    /// encode the subject in CBOR.
    #[serde(rename = "sub", with = "HexForm")]
    subject: [u8; 32],
}

let key = Hs256Key::from(b"super_secret_key_donut_steel" as &[_]);
let claims = Claims::new(CustomClaims { subject: [111; 32] })
    .set_duration_and_issuance(Duration::days(7));
let token = Hs256.token(Header::default(), &claims, &key)?;
println!("token: {}", token);
let compact_token = Hs256.compact_token(Header::default(), &claims, &key)?;
println!("compact token: {}", compact_token);
// The compact token should be ~40 chars shorter.

// Parse the compact token.
let token = UntrustedToken::try_from(compact_token.as_str())?;
let token: Token<CustomClaims> = Hs256.validate_integrity(&token, &key)?;
token.claims().validate_expiration(TimeOptions::default())?;
// Now, we can extract information from the token (e.g., its subject).
assert_eq!(token.claims().custom.subject, [111; 32]);

Dependencies

~8–14MB
~231K SLoC