Verify JWT in Rust (jsonwebtoken)
Verify JWT signatures in Rust with the jsonwebtoken crate. Sign and verify HS256 and RS256 tokens, validate iss/aud/exp claims, and verify JWTs against a JWKS endpoint with full Rust code examples.
Rust’s standard JWT library is the jsonwebtoken crate. It supports HS256/384/512 and RS256/384/512 natively; ES256/384 and EdDSA support varies by version — check the release notes for your pinned version before adopting those.
Install
[dependencies]
jsonwebtoken = "9"
serde = { version = "1", features = ["derive"] }
Sign and verify an HS256 token
use jsonwebtoken::{encode, decode, EncodingKey, DecodingKey, Algorithm, Validation, Header};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
iss: String,
aud: String,
exp: usize, // unix seconds — usize matches the crate's expectation
}
let secret = std::env::var("JWT_SECRET").unwrap(); // 32+ bytes
// Sign
let claims = Claims {
sub: "user_123".into(),
iss: "https://auth.example.com".into(),
aud: "https://api.example.com".into(),
exp: chrono::Utc::now().timestamp() as usize + 15 * 60,
};
let token = encode(&Header::new(Algorithm::HS256), &claims, &EncodingKey::from_secret(secret.as_bytes()))?;
// Verify — hardcoded algorithm, explicit iss + aud
let mut validation = Validation::new(Algorithm::HS256);
validation.leeway = 60; // 60s skew
validation.iss = Some("https://auth.example.com".to_string());
validation.aud = Some(std::collections::HashSet::from(["https://api.example.com".to_string()]));
validation.validate_exp = true;
let token_data = decode::<Claims>(&token, &DecodingKey::from_secret(secret.as_bytes()), &validation)?;
println!("{}", token_data.claims.sub); // "user_123"
Sign and verify an RS256 token
let private_pem = std::fs::read("rsa-private.pem")?;
let public_pem = std::fs::read("rsa-public.pem")?;
let token = encode(
&Header::new(Algorithm::RS256),
&claims,
&EncodingKey::from_rsa_pem(&private_pem)?,
)?;
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_rsa_pem(&public_pem)?,
&Validation::new(Algorithm::RS256),
)?;
Verify a JWT against a JWKS endpoint
The jsonwebtoken crate does not ship a JWKS client. Use jwks-client (or fetch the JWKS yourself) and construct a DecodingKey from the matching JWK:
use jsonwebtoken::DecodingKey;
// Fetch + cache the JWKS (use jwks-client crate or your own cache with a TTL)
let jwks: serde_json::Value = reqwest::get("https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json").await?.json().await?;
// Find the key matching the token header kid
let header = jsonwebtoken::decode_header(&token)?;
let kid = header.kid.ok_or("missing kid")?;
let jwk = jwks["keys"].as_array().unwrap().iter()
.find(|k| k["kid"] == kid).ok_or("unknown kid")?;
// Build an RSA DecodingKey from the JWK's n and e
let n = jwk["n"].as_str().unwrap();
let e = jwk["e"].as_str().unwrap();
let decoding_key = DecodingKey::from_rsa_components(n, e)?; // jsonwebtoken 9.2+
let token_data = decode::<Claims>(&token, &decoding_key, &Validation::new(Algorithm::RS256))?;
Cache the parsed DecodingKey objects in your process or a Redis-backed cache with a 1-hour TTL. Do not fetch the JWKS on every request.
Validation order
decode performs signature verification and exp/nbf validation in one call. iss and aud are checked when you populate the corresponding Validation fields. The crate does not validate iat — if you need a max-age check, compare iat to the current time yourself after a successful decode.
Continue reading
- JWT decoder → - paste a token to decode and verify it in your browser
- JWT Signing Algorithms → - HS256, RS256, ES256, EdDSA with key sizes and code
- Registered Claims Reference → - iss, sub, aud, exp, jti, and every RFC 7519 claim
- JWT Security Vulnerabilities → - alg:none, algorithm confusion, kid injection, and every fix
- Verify JWT in Node.js → - jsonwebtoken and jose code
- Verify JWT in Java → - Spring Security and jjwt, including the aud mismatch fix