oxitoken

Crates.iooxitoken
lib.rsoxitoken
version0.1.0
created_at2025-11-19 07:22:41.036176+00
updated_at2025-11-19 07:22:41.036176+00
descriptionFastest and most secure JWT encoding/validation library for Rust. Fully supports custom header/claims structs, an extendable validation pipeline, and is crypto-backend agnostic (built-in support for aws-lc-rs and openssl).
homepage
repository
max_upload_size
id1939569
size16,526,476
(dsykes16)

documentation

README

OxiToken

oxitoken strives to be the fastest JWT encoding/decoding/validation library while simulteneously providing the best developer experience and extensibility.

  • Custom header and claims structs, no stringly-typed extras HashMap here
  • Custom KeyProvider support
    • A LocalKeystore implementation is provided that resolves keys via kid
    • If that doesn't fit your use-case, implement your own KeyProvider, which can resolve keys via any abitrary header or claim field, even custom ones
  • Crypto backend agnostic
    • Reference implementations using aws-lc-rs and openssl are provided
      • Signer and VerificationKey traits permit using your own crypto backend (e.g. botan-rs, ring, HSMs, etc.)
      • Built-in providers can be fully disabled with feature flags
  • Supports all the IANA registered JWS algorithms
    • HMAC: HS256, HS384, HS512
    • RSA: RS256, RS384, RS512, PS256, PS384, PS512
    • ECDSA: ES256, ES384, ES512
    • EdDSA: EdDSA (i.e. Ed25519) and Ed448
    • Post-Quantum algorithms: ML-DSA-44, ML-DSA-65, ML-DSA-87

Benchmarks

Encoding Performance

Algorithm oxitoken jsonwebtoken % faster
HS256 312.45 ns 638.67 ns 51.08 %
HS384 417.19 ns 856.24 ns 51.28 %
HS512 422.99 ns 862.96 ns 50.98 %
RS256 480.22 µs 762.61 µs 37.03 %
RS384 1525.74 µs 2056.15 µs 25.8 %
RS512 3234.4 µs 4051.46 µs 20.17 %
PS256 480.72 µs 765.89 µs 37.23 %
PS384 1529.31 µs 2098.04 µs 27.11 %
PS512 3252.92 µs 4072.99 µs 20.13 %
ES256 16.47 µs 25.57 µs 35.58 %
ES384 69.61 µs 112.13 µs 37.92 %
EdDSA 6.51 µs 12.48 µs 47.85 %
ML-DSA-44 140.91 µs
ML-DSA-65 230.96 µs
ML-DSA-87 284.98 µs

Validation Performance

Algorithm oxitoken jsonwebtoken % faster
HS256 623.51 ns 1.7 µs 63.28 %
HS384 788.75 ns 2 µs 60.55 %
HS512 789.81 ns 2.05 µs 61.48 %
RS256 13.48 µs 17.13 µs 21.35 %
RS384 26.87 µs 32.88 µs 18.28 %
RS512 45.97 µs 53.9 µs 14.71 %
PS256 13.34 µs 17.39 µs 23.3 %
PS384 27.77 µs 33.55 µs 17.21 %
PS512 46.92 µs 54.86 µs 14.48 %
ES256 42.98 µs 44.55 µs 3.52 %
ES384 163.61 µs 165.55 µs 1.17 %
EdDSA 27.89 µs 28.98 µs 3.77 %
ML-DSA-44 39.19 µs
ML-DSA-65 63.38 µs
ML-DSA-87 104.22 µs

Comparative benchmarks above were ran with aws-lc-rs backend for both oxitoken and jsonwebtoken. openssl is slightly slower than aws-lc-rs across the board, and rust_crypto is substantially slower.

Example

use aws_lc_rs::signature::{
    ED25519,
    Ed25519KeyPair,
    KeyPair,
    ParsedPublicKey,
};
use oxitoken::{
    Algorithm,
    claims::{
        Exp,
        Iss,
    },
    encoding::encode,
    header::Alg,
    validation::{
        StaticKeyProvider,
        ValidationPipeline,
    },
};

// NOTE: Debug, PartialEq, and Eq are only required for the assertions in this
// example code and are not a requirement of oxitoken.
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
struct Header {
    alg: Algorithm,
}
impl Header {
    const fn new(alg: Algorithm) -> Self {
        Self { alg }
    }
}
impl Alg for Header {
    fn alg(&self) -> Algorithm {
        self.alg
    }
}

// NOTE: Debug, PartialEq, and Eq are only required for the assertions in this
// example code and are not a requirement of oxitoken.
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
struct Claims {
    iss: String,
    exp: i64,
}
impl Claims {
    fn new(iss: impl Into<String>) -> Self {
        Self {
            iss: iss.into(),
            exp: 0,
        }
    }
}
impl Iss for Claims {
    fn iss(&self) -> &str {
        &self.iss
    }
}
impl Exp for Claims {
    fn exp(&self) -> i64 {
        self.exp
    }
}

fn make_key() -> Ed25519KeyPair {
    Ed25519KeyPair::generate().unwrap()
}

fn make_parsed_public_key(key: &Ed25519KeyPair) -> ParsedPublicKey {
    ParsedPublicKey::new(&ED25519, key.public_key().as_ref()).unwrap()
}

fn main() {
    let header = Header::new(Algorithm::EdDSA);
    let claims = Claims::new("oxitoken.example.org");
    let key = make_key();
    let jwt = encode(&key, &header, &claims).unwrap();
    println!("JWT: {jwt}");

    let pubkey = make_parsed_public_key(&key);
    let validator =
        ValidationPipeline::<Header, Claims, StaticKeyProvider<_>>::builder(pubkey.into())
            .with_issuer_validator("oxitoken.example.org")
            .with_expiration_validator()
            .build();

    let (decoded_header, decoded_claims) = validator.verify(jwt.as_bytes()).unwrap();

    assert_eq!(decoded_header, header);
    assert_eq!(decoded_claims, claims);

    println!("JWT validated successfully");

    // an alternative that allows the compiler to infer the generic types,
    // permitting omittance of the turbofish.
    let pubkey = StaticKeyProvider::from(make_parsed_public_key(&key));
    let validator = ValidationPipeline::builder(pubkey)
        .with_issuer_validator("oxitoken.example.org")
        .build();
    let (decoded_header, decoded_claims): (Header, Claims) =
        validator.verify(jwt.as_bytes()).unwrap();

    assert_eq!(decoded_header, header);
    assert_eq!(decoded_claims, claims);
}
Commit count: 0

cargo fmt