Introduction
\nLe développement web moderne est impensable sans une authentification et une autorisation sécurisées des utilisateurs. Les deux normes les plus populaires — OAuth 2.0 et JWT (JSON Web Tokens) — sont devenues des solutions industrielles de facto. Si vous programmez en Rust, vous avez de la chance : ce langage offre non seulement les plus hautes performances, mais aussi des bibliothèques puissantes pour implémenter ces protocoles.
\nDans cet article, nous allons voir ce que sont OAuth 2.0 et JWT, comment ils fonctionnent ensemble, et écrire un exemple simple mais fonctionnel d'autorisation en Rust en utilisant le framework actix-web et la crate jsonwebtoken. L'article est destiné aux débutants, nous allons donc procéder étape par étape — de la théorie au code.
Que sont OAuth 2.0 et JWT ?
\nAvant d'écrire du code, il est important de comprendre les concepts. OAuth 2.0 est un protocole d'autorisation qui permet aux applications d'obtenir un accès limité aux ressources d'un utilisateur sans lui transmettre son mot de passe. Il est utilisé lorsque vous vous connectez à un site via Google, GitHub ou VK.
\nJWT est un format de jeton (JSON Object Signing and Encryption) qui contient des assertions (claims) sur l'utilisateur. Le jeton est signé par le serveur, donc le client ne peut pas le falsifier. Dans le contexte d'OAuth 2.0, JWT est souvent utilisé comme access token (jeton d'accès).
\nScénario typique : l'utilisateur saisit son identifiant et son mot de passe, le serveur les vérifie, génère un jeton JWT et le renvoie au client. Le client envoie ce jeton dans l'en-tête Authorization: Bearer <token> à chaque requête. Le serveur vérifie la signature et extrait les données de l'utilisateur.
Configuration du projet en Rust
\nCréons un nouveau projet :
\ncargo new rust_oauth_jwt\ncd rust_oauth_jwt\nAjoutons les dépendances nécessaires dans Cargo.toml :
[dependencies]\nactix-web = "4"\nactix-rt = "2"\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"\njsonwebtoken = "9"\nchrono = { version = "0.4", features = ["serde"] }\ndotenv = "0.15"\nenv_logger = "0.11"\nlog = "0.4"\nCréons un fichier .env à la racine du projet pour stocker la clé secrète :
JWT_SECRET=my_super_secret_key_change_in_production\nJWT_EXPIRATION_HOURS=24\nChargeons les variables d'environnement dans main.rs :
use dotenv::dotenv;\nuse std::env;\n\nfn main() {\n dotenv().ok();\n env_logger::init();\n // ... reste du code\n}\n\nCréation de la structure utilisateur et du jeton JWT
\nDéfinissons d'abord le modèle utilisateur et la structure des claims pour JWT. Créons un fichier models.rs :
use serde::{Deserialize, Serialize};\n\n#[derive(Debug, Serialize, Deserialize, Clone)]\npub struct User {\n pub id: i32,\n pub username: String,\n pub password_hash: String, // Dans un vrai projet, stocker le hash, pas le mot de passe !\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct Claims {\n pub sub: String, // subject (id utilisateur)\n pub username: String,\n pub exp: usize, // timestamp d'expiration\n}\nCréons maintenant un utilitaire pour travailler avec JWT dans jwt_utils.rs :
use chrono::{Duration, Utc};\nuse jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};\nuse crate::models::Claims;\n\npub fn create_jwt(user_id: &str, username: &str) -> Result<String, jsonwebtoken::errors::Error> {\n let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");\n let expiration_hours: i64 = std::env::var("JWT_EXPIRATION_HOURS")\n .unwrap_or_else(|_| "24".to_string())\n .parse()\n .unwrap();\n\n let expiration = Utc::now()\n .checked_add_signed(Duration::hours(expiration_hours))\n .expect("valid timestamp")\n .timestamp() as usize;\n\n let claims = Claims {\n sub: user_id.to_owned(),\n username: username.to_owned(),\n