Introduction
\nModern web development is unthinkable without secure user authentication and authorization. The two most popular standards — OAuth 2.0 and JWT (JSON Web Tokens) — have become de facto industry solutions. If you are writing in Rust, you are in luck: this language offers not only the highest performance but also powerful libraries for implementing these protocols.
\nIn this article, we will break down what OAuth 2.0 and JWT are, how they work together, and write a simple but working authorization example in Rust using the actix-web framework and the jsonwebtoken crate. The article is aimed at beginners, so we will go step by step — from theory to code.
What are OAuth 2.0 and JWT?
\nBefore writing code, it is important to understand the concepts. OAuth 2.0 is an authorization protocol that allows applications to obtain limited access to user resources without transmitting their password. It is used when you log into a website via Google, GitHub, or VK.
\nJWT is a token format (JSON Object Signing and Encryption) that contains claims about the user. The token is signed by the server, so the client cannot forge it. In the context of OAuth 2.0, JWT is often used as an access token.
\nA typical scenario: the user enters a login and password, the server verifies them, generates a JWT token, and returns it to the client. The client sends this token in the Authorization: Bearer <token> header with each request. The server verifies the signature and extracts the user data.
Setting Up a Rust Project
\nLet's create a new project:
\ncargo new rust_oauth_jwt\ncd rust_oauth_jwt\nAdd the necessary dependencies to 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"\nCreate a .env file in the project root to store the secret key:
JWT_SECRET=my_super_secret_key_change_in_production\nJWT_EXPIRATION_HOURS=24\nLoad the environment variables in main.rs:
use dotenv::dotenv;\nuse std::env;\n\nfn main() {\n dotenv().ok();\n env_logger::init();\n // ... rest of the code\n}\n\nCreating the User Structure and JWT Token
\nFirst, let's define the user model and the claims structure for JWT. Create a file 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, // In a real project, store the hash, not the password!\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}\nNow create a utility for working with JWT in 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