Minimal JSON Web Token (JWT) validation for Rust.
jwtiny validates JWT tokens efficiently in production Rust applications. The validator follows a reusable pattern: configure it once at application startup, then verify tokens with minimal allocations. The validator can be shared across requests, which reduces memory footprint and improves performance.
The library supports RSA (RS256, RS384, RS512) and ECDSA (ES256, ES384, ES512) algorithms with an aws-lc-rs backend, and provides JWKS support for remote key fetching over HTTPS (rustls) with caching. It's been tested with Axum, Poem, Rocket, and Warp.
Add jwtiny to your Cargo.toml:
cargo add jwtinyWhen you have a static public key, configure the validator like this:
use std::sync::Arc;
use jwtiny::{AlgorithmPolicy, ClaimsValidation, TokenValidator};
let validator = TokenValidator::new()
.algorithms(AlgorithmPolicy::rs512_only())
.validate(ClaimsValidation::default())
.key(Arc::new(public_key_der));
let claims = validator.verify(token).await?;That's it! The validator's ready to use. You can call verify() as many times as you need—the library is designed for reuse.
For production systems, you'll often want to fetch keys from a JWKS endpoint. Here's how the library sets this up:
use jwtiny::{AlgorithmPolicy, ClaimsValidation, TokenValidator};
use moka::future::Cache;
use std::time::Duration;
let client = reqwest::Client::new();
let cache = Cache::<String, Vec<u8>>::builder()
.time_to_live(Duration::from_secs(300))
.max_capacity(1000)
.build();
let validator = TokenValidator::new()
.algorithms(AlgorithmPolicy::rs512_only())
.issuer(|iss| iss == "https://auth.example.com")
.validate(ClaimsValidation::default().require_audience("my-api"))
.jwks(client)
.cache(cache);
let claims = validator.verify(token).await?;The cache reduces network requests and improves performance. Set the TTL to match the key rotation schedule of the identity provider.
For testing, this works fine with JWKServe as well.
If you need custom claim structures, use the #[claims] macro:
use std::sync::Arc;
use jwtiny::{claims, AlgorithmPolicy, ClaimsValidation, TokenValidator};
#[claims]
struct MyClaims {
pub role: String,
pub permissions: Vec<String>,
}
let validator = TokenValidator::new()
.algorithms(AlgorithmPolicy::rs256_only())
.validate(ClaimsValidation::default())
.key(Arc::new(public_key_der));
let claims = validator.verify_with_custom::<MyClaims>(token).await?;The macro handles the standard claims (iss, sub, aud, exp, nbf, iat, jti) automatically, so you only need to define your custom fields.
For ECDSA algorithms, jwtiny is particularly efficient — ES384 performance is over 3x faster than jsonwebtoken, while ES256 shows a solid 8% improvement.
The RSA performance gains scale with key size: you'll see roughly 18–20% improvements with 2048-bit keys, 26–27% with 3072-bit keys, and around 30–31% with 4096-bit keys, regardless of the hash variant.
These improvements become more pronounced as cryptographic operations become computationally expensive, making jwtiny especially beneficial for high-throughput applications or services handling many concurrent token validations.
The throughput of RS256 degrades ~35% from default token (60,203 ops/s at 550 bytes) to +1000% token size (39,173 ops/s at 7,830 bytes), while ES384 stays stable with only ~16% degradation (5,696 to 4,807 ops/s) despite a 14x token size increase.
At +1000% token size, jwtiny’s RS256 advantage narrows to ~5%, while ES384 maintains its ~3x advantage, indicating ES384’s validation is less sensitive to payload size than RS256.
Configure the validator once, then reuse it for multiple verifications:
use std::sync::Arc;
let validator = TokenValidator::new()
.algorithms(AlgorithmPolicy::rs512_only()) // See AlgorithmPolicy section below
.issuer(|iss| iss == "https://auth.example.com")
.validate(ClaimsValidation::default().require_audience("my-api"))
.key(Arc::new(public_key_der)) // Wrap in Arc for efficient sharing
.jwks(client) // JWKS (mutually exclusive with key)
.cache(cache); // Optional: cache JWKS keys
// Verify tokens (reusable)
let claims = validator.verify(token_str).await?;
let custom = validator.verify_with_custom::<MyClaims>(token_str).await?;Control which algorithms are accepted:
use jwtiny::{AlgorithmPolicy, AlgorithmType};
// Predefined policies (zero-allocation, use stack arrays)
AlgorithmPolicy::rs256_only() // RS256 only
AlgorithmPolicy::rs384_only() // RS384 only
AlgorithmPolicy::rs512_only() // RS512 only
AlgorithmPolicy::rsa_all() // All RSA algorithms
AlgorithmPolicy::es256_only() // ES256 (P-256) only
AlgorithmPolicy::es384_only() // ES384 (P-384) only
AlgorithmPolicy::es512_only() // ES512 (P-521) only
AlgorithmPolicy::ecdsa_all() // All ECDSA algorithms
// Custom policies (accepts arrays)
AlgorithmPolicy::allow_only([AlgorithmType::RS256, AlgorithmType::ES256])Note: The default policy is rs256_only() for security. Always configure the policy explicitly to match your identity provider's signing algorithm.
Configure temporal and audience validation:
ClaimsValidation::default()
.require_audience("my-api")
.max_age(3600)
.clock_skew(60)
.no_exp_validation()
.no_nbf_validation()
.no_iat_validation()By default, the validator checks expiration (exp), not-before (nbf), and issued-at (iat), with a max age of 30 minutes and no clock skew. In distributed systems, adding clock skew tolerance can help handle time synchronisation differences.
All validation errors are returned as jwtiny::Error:
match validator.verify(token).await {
Ok(claims) => println!("Valid: {:?}", claims),
Err(jwtiny::Error::TokenExpired { .. }) => eprintln!("Token expired"),
Err(jwtiny::Error::SignatureInvalid) => eprintln!("Invalid signature"),
Err(e) => eprintln!("Validation failed: {:?}", e),
}Complete working examples for various web frameworks:
- Axum:
examples/axum.rs - Poem:
examples/poem.rs - Rocket:
examples/rocket.rs - Warp:
examples/warp.rs
Run an example:
# Run example: axum, poem, rocket, or warp
$ > cargo run --example axumNaive performance benchmarks are available to measure validation throughput and latency:
- RSA Validation — Signature validation performance for RSA 2048 keys with SHA-256, SHA-384, and SHA-512 algorithms
- Token Size — Validation performance across varying JWT payload sizes (baseline to +2000%)
- JWKS Validation — Remote JWKS endpoint validation with and without caching for SHA-256, SHA-384, and SHA-512
- Rocket Integration — End-to-end validation performance through Rocket middleware with and without caching
Run benchmarks:
# Run all benchmarks
$ > cargo bench
# Run a specific benchmark
$ > cargo bench --bench rsa_validation
$ > cargo bench --bench token_size
$ > cargo bench --bench jwks_validation
$ > cargo bench --bench rocket_integrationBenchmark results are exported to ./reports/ as plain text files for analysis and charting.
MIT

