Introducción
\nEl desarrollo web moderno es impensable sin autenticación y autorización segura de usuarios. Los dos estándares más populares — OAuth 2.0 y JWT (JSON Web Tokens) — se han convertido en soluciones industriales de facto. Si programas en Rust, tienes suerte: este lenguaje ofrece no solo el más alto rendimiento, sino también potentes bibliotecas para implementar estos protocolos.
\nEn este artículo, analizaremos qué son OAuth 2.0 y JWT, cómo funcionan juntos, y escribiremos un ejemplo simple pero funcional de autorización en Rust utilizando el framework actix-web y el crate jsonwebtoken. El artículo está orientado a principiantes, por lo que avanzaremos paso a paso, desde la teoría hasta el código.
¿Qué son OAuth 2.0 y JWT?
\nAntes de escribir código, es importante entender los conceptos. OAuth 2.0 es un protocolo de autorización que permite a las aplicaciones obtener acceso limitado a los recursos del usuario sin transmitir su contraseña. Se utiliza cuando inicias sesión en un sitio web a través de Google, GitHub o VK.
\nJWT es un formato de token (JSON Object Signing and Encryption) que contiene afirmaciones (claims) sobre el usuario. El token es firmado por el servidor, por lo que el cliente no puede falsificarlo. En el contexto de OAuth 2.0, JWT se utiliza a menudo como access token (token de acceso).
\nEscenario típico: el usuario ingresa su nombre de usuario y contraseña, el servidor los verifica, genera un token JWT y lo devuelve al cliente. El cliente envía este token en cada solicitud en el encabezado Authorization: Bearer <token>. El servidor verifica la firma y extrae los datos del usuario.
Configuración del proyecto en Rust
\nCreemos un nuevo proyecto:
\ncargo new rust_oauth_jwt\ncd rust_oauth_jwt\nAgreguemos las dependencias necesarias en 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"\nCreemos un archivo .env en la raíz del proyecto para almacenar la clave secreta:
JWT_SECRET=my_super_secret_key_change_in_production\nJWT_EXPIRATION_HOURS=24\nCarguemos las variables de entorno en main.rs:
use dotenv::dotenv;\nuse std::env;\n\nfn main() {\n dotenv().ok();\n env_logger::init();\n // ... resto del código\n}\n\nCreación de la estructura de usuario y token JWT
\nPrimero, definamos el modelo de usuario y la estructura de claims para JWT. Creemos el archivo 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, // ¡En un proyecto real, almacenar el hash, no la contraseña!\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct Claims {\n pub sub: String, // subject (user id)\n pub username: String,\n pub exp: usize, // expiration timestamp\n}\nAhora, creemos una utilidad para trabajar con JWT en 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