JSON Web Token Claims Expiry Validation

Sign and verify tokens with jsonwebtoken in Rust, validating registered claims, required fields, and clock tolerance windows.

RustAuth Jwt

Code

snippet
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Serialize, Deserialize)]
struct SessionClaims {
    sub: String,
    role: String,
    tenant: String,
    exp: u64,
    iat: u64,
}

fn now() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs()
}

fn issue(key: &EncodingKey, subject: &str, role: &str, tenant: &str) -> String {
    let claims = SessionClaims {
        sub: subject.into(),
        role: role.into(),
        tenant: tenant.into(),
        iat: now(),
        exp: now() + 900, // 15 分钟访问令牌
    };
    encode(&Header::new(Algorithm::ES256), &claims, key).unwrap()
}

fn verify(
    key: &DecodingKey,
    token: &str,
    required_tenant: &str,
) -> Result<SessionClaims, String> {
    let mut validation = Validation::new(Algorithm::ES256);
    validation.required_spec_claims.insert("exp".into());
    validation.required_spec_claims.insert("sub".into());
    validation.leeway = 5; // 时钟偏差容忍 5 秒
    validation.validate_exp = true;

    let data = decode::<SessionClaims>(token, key, &validation)
        .map_err(|err| format!("token rejected: {err:?}"))?;

    if data.claims.tenant != required_tenant {
        return Err("tenant mismatch".into());
    }
    Ok(data.claims)
}

fn main() {
    let encoding = EncodingKey::from_ec_pem(include_bytes!("../keys/private.pem")).unwrap();
    let decoding = DecodingKey::from_ec_pem(include_bytes!("../keys/public.pem")).unwrap();

    let token = issue(&encoding, "user-42", "editor", "acme");
    match verify(&decoding, &token, "acme") {
        Ok(claims) => println!("accepted: {} as {}", claims.sub, claims.role),
        Err(err) => println!("rejected: {err}"),
    }

    match verify(&decoding, &token, "other-tenant") {
        Ok(_) => unreachable!(),
        Err(err) => println!("cross-tenant blocked: {err}"),
    }
}