JWT (autonome)
Un JSON Web Token est un identifiant sans état : une chaîne signée et
autonome que le client envoie à chaque requête, et que votre serveur vérifie
avec un secret — sans consultation de base de données ni de cache à chaque
requête. Le module rustango::jwt de Rustango est la brique minimale :
encode pour signer des claims, decode pour les vérifier et les relire,
HS256 en interne.
Source :
rustango::jwt(Claims,encode,decode,decode_at,decode_unverified,JwtError) — derrière la fonctionnalitéjwt(activée par défaut). Pour une API access+refresh clé en main avec révocation, voir API d'authentification JWT.Version exécutable : les extraits sont copiés depuis le test
auth_demo—cargo test -p auth_demo --test auth_jwt.
Un terme vous est inconnu ? JWT, claims, sans état, secret — voir le glossaire.
Complément approfondi de la section « Émettre et rafraîchir des JWT » du Guide de sécurité.
Table des matières
- Démarrage rapide · Quand l'utiliser
- Construire des claims · Vérifier
- Modèle de sécurité — à lire · Inspecter sans faire confiance
- Notes et limites
Démarrage rapide
use rustango::jwt::{Claims, encode, decode};
use std::time::Duration;
// HS256 est symétrique — le même secret signe et vérifie. Doit faire >= 32 octets.
let secret = b"a-shared-signing-secret-at-least-32-bytes!!";
let mut claims = Claims::new("user-42").ttl(Duration::from_secs(900));
claims.set("roles", vec!["editor", "author"]);
let token = encode(&claims, secret)?; // header.payload.signature
let verified = decode(&token, secret)?; // vérifie la signature + exp/nbf
assert_eq!(verified.subject(), Some("user-42"));
let roles: Vec<String> = verified.get("roles").unwrap();
Quand utiliser un JWT autonome
Optez pour rustango::jwt quand vous voulez un simple token signé et que vous
gérerez le cycle de vie vous-même :
- Liens magiques / tokens à usage unique — quelques claims (id utilisateur,
finalité,
expcourt). Voir Liens magiques et flux d'authentification. - Tokens bearer de service à service (le pendant JWT de la signature de requêtes HMAC — HMAC pour les requêtes canoniques façon AWS, JWT pour un bearer sans état).
- Tokens SSO que vous remettez à un tiers.
Si vous voulez une API clé en main login → access + refresh → refresh → logout avec révocation de tokens, ne la construisez pas là-dessus — utilisez l'API d'authentification JWT, qui enveloppe ce module avec rotation + un magasin de révocation. Et si vous devez déconnecter un utilisateur de force maintenant, préférez une Session révocable : un JWT simple reste valide jusqu'à son expiration.
Construire des claims
Claims enveloppe un objet JSON, de sorte que les claims standard et vos propres
champs d'extension coexistent :
let mut claims = Claims::new("user-42") // définit `sub` + `iat=now`
.ttl(Duration::from_secs(3600)) // définit `iat`=now et `exp`=now+ttl
.issuer("api.example.com") // `iss`
.audience("web-client") // `aud`
.jti("unique-token-id"); // `jti` (pour votre propre liste de blocage)
claims.set("role", "admin"); // toute valeur Serialize
claims.set("org_id", 7_i64);
| Builder / setter | Claim |
|---|---|
Claims::new(sub) | sub + iat |
Claims::empty() | aucune (contrôle total) |
.ttl(Duration) | iat (now) + exp (now+ttl) |
.expires_at(secs) / .not_before(secs) | exp / nbf absolus |
.issuer(s) / .audience(s) / .jti(s) | iss / aud / jti |
.set(name, value) | tout claim personnalisé |
Relisez-les avec .subject() et .get::<T>(name) (renvoie None pour un claim
absent ou du mauvais type).
Vérifier un token
use rustango::jwt::{decode, JwtError};
match decode(&token, secret) {
Ok(claims) => { /* faire confiance à claims.subject() etc. */ }
Err(JwtError::Expired(_)) => { /* 401 — token périmé */ }
Err(JwtError::BadSignature) => { /* 401 — falsifié ou mauvaise clé */ }
Err(JwtError::NotYetValid(_)) => { /* nbf dans le futur */ }
Err(_) => { /* malformé / alg non supporté */ }
}
decode vérifie la signature, puis exp et nbf. Pour tester le
comportement de la fenêtre temporelle (ou ajouter une tolérance de dérive),
decode_at(token, secret, now) vous permet de fixer la seconde « courante » :
let token = encode(&Claims::new("x").expires_at(1000), secret)?;
assert!(decode_at(&token, secret, 500).is_ok()); // avant exp
assert!(matches!(decode_at(&token, secret, 2000), Err(JwtError::Expired(_)))); // après
Modèle de sécurité
C'est du code de frontière d'authentification — trois choses à savoir impérativement :
-
decodene valide PASiss/aud. Une signature valide prouve que le token a été forgé avec votre secret, pas qu'il a été forgé pour votre service. Si vous définisseziss/audà l'émission, vérifiez-les vous-même sur les claims décodés :let c = decode(&token, secret)?; if c.get::<String>("aud").as_deref() != Some("web-client") { return Err("wrong audience"); } -
Le secret doit faire ≥ 32 octets —
encoderefuse de signer avec une clé plus courte (une clé courte est devinable, et une clé HMAC devinable signifie des tokens falsifiables). HS256 est symétrique : quiconque possède le secret de vérification peut aussi forger des tokens, il reste donc à l'intérieur de votre frontière de confiance (service unique / backend partagé). L'émission de tokens inter-organisations demande du RS256/ES256 asymétrique, que ce module ne fournit délibérément pas. -
alg=noneet la falsification sont rejetés.decodefige HS256 (la falsification classique « alg: none » est refusée), et toute modification de l'en-tête ou du payload casse la signature — vérifiée par une comparaison à temps constant.
Il n'y a aucune tolérance de dérive d'horloge : exp/nbf se comparent à la
seconde courante exacte. Si les horloges de l'émetteur et du vérificateur
dérivent, soustrayez quelques secondes via decode_at.
Inspecter sans vérifier
decode_unverified lit le payload sans vérifier la signature ni
l'expiration — utile uniquement pour jeter un œil à un claim (p. ex. un id de
clé) afin de choisir le bon secret, puis d'appeler decode pour de vrai.
let peek = rustango::jwt::decode_unverified(&token)?; // PAS de confiance
let kid = peek.get::<String>("kid");
// ... rechercher le secret pour `kid`, puis vérifier correctement :
let claims = decode(&token, &resolved_secret)?;
N'autorisez jamais sur la sortie de decode_unverified — elle ne porte
aucune garantie d'intégrité.
Notes et limites
- HS256 uniquement — symétrique, un seul secret partagé. Pas de RS256/ES256 (garde l'arbre de dépendances toujours actif réduit ; la plupart des applications mono-service utilisent HS256 de toute façon).
- Sans état = non révocable. Un JWT simple est valide jusqu'à
exp. Si vous avez besoin de « déconnexion immédiate » / de révocation par token, utilisez l'API d'authentification JWT (liste de blocage JTI) ou une Session (supprimez l'entrée côté serveur). - Gardez
expcourt pour les access tokens (quelques minutes). Les JWT simples à longue durée de vie sont un risque précisément parce qu'ils ne peuvent pas être révoqués. - Associez l'émission aux Mots de passe (vérifier, puis
émettre) et protégez les routes d'API via le
JwtBackendde la chaîne de backends d'authentification.
