OAuth 2.0 и JWT авторизация на Rust: пошаговое руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: зачем OAuth 2.0 и JWT в Rust?

Современные веб-приложения редко обходятся без системы аутентификации и авторизации. OAuth 2.0 — это отраслевой стандарт для делегирования доступа, а JWT (JSON Web Tokens) — компактный и безопасный способ передачи утверждений между сторонами. Rust, благодаря своей производительности и безопасности памяти, становится всё более популярным для создания высоконагруженных серверов. В этой статье мы на практике разберём, как реализовать OAuth 2.0 авторизацию с JWT на Rust, используя популярные крейты и современные подходы.

1. Подготовка проекта и установка зависимостей

Для начала создадим новый проект на Rust и добавим необходимые зависимости. Мы будем использовать actix-web для веб-сервера, jsonwebtoken для работы с JWT, serde для сериализации и reqwest для HTTP-запросов к внешним провайдерам OAuth.

# Cargo.toml
[package]
name = "oauth-jwt-rust"
version = "0.1.0"
edition = "2021"

[dependencies]
actix-web = "4"
actix-rt = "2"
jsonwebtoken = "9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
dotenv = "0.15"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4"] }

Создадим файл .env для хранения секретных ключей:

JWT_SECRET=your_super_secret_key_here
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret

2. Базовая структура JWT токенов

JWT состоит из трёх частей: заголовок, полезная нагрузка и подпись. Определим структуры для работы с токенами.

// src/jwt.rs
use chrono::{Utc, Duration};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String,       // идентификатор пользователя
    pub email: String,     // email пользователя
    pub exp: usize,        // время истечения
    pub iat: usize,        // время выдачи
}

pub fn create_jwt(user_id: &str, email: &str) -> Result {
    let now = Utc::now();
    let expiration = now + Duration::hours(24); // токен живёт 24 часа

    let claims = Claims {
        sub: user_id.to_string(),
        email: email.to_string(),
        exp: expiration.timestamp() as usize,
        iat: now.timestamp() as usize,
    };

    let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
    encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(secret.as_bytes()),
    )
}

pub fn validate_jwt(token: &str) -> Result {
    let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
    let token_data = decode::(
        token,
        &DecodingKey::from_secret(secret.as_bytes()),
        &Validation::default(),
    )?;
    Ok(token_data.claims)
}

3. Реализация OAuth 2.0 с GitHub

Рассмотрим классический сценарий OAuth 2.0 — авторизация через GitHub. Пользователь перенаправляется на GitHub, после подтверждения получает код, который мы обмениваем на access token.

3.1. Эндпоинт для начала OAuth-потока

// src/oauth.rs
use actix_web::{web, HttpResponse, HttpRequest};
use reqwest::Client;
use serde_json::Value;

pub async fn github_login() -> HttpResponse {
    let client_id = std::env::var("GITHUB_CLIENT_ID").unwrap();
    let redirect_uri = "http://localhost:8080/auth/github/callback";
    
    let auth_url = format!(
        "https://github.com/login/oauth/authorize?client_id={}&redirect_uri={}&scope=user:email",
        client_id, redirect_uri
    );
    
    HttpResponse::Found()
        .append_header(("Location", auth_url))
        .finish()
}

3.2. Callback-обработчик

pub async fn github_callback(query: web::Query<std::collections::HashMap<String, String>>) -> HttpResponse {
    let code = match query.get("code") {
        Some(c) => c,
        None => return HttpResponse::BadRequest().body("Missing code parameter"),
    };

    let client = Client::new();
    let client_id = std::env::var("GITHUB_CLIENT_ID").unwrap();
    let client_secret = std::env::var("GITHUB_CLIENT_SECRET").unwrap();

    // Обмен кода на access token
    let token_params = [
        ("client_id", client_id.as_str()),
        ("client_secret", client_secret.as_str()),
        ("code", code),
    ];

    let token_response = client
        .post("https://github.com/login/oauth/access_token")
        .header("Accept", "application/json")
        .form(&token_params)
        .send()
        .await
        .unwrap();

    let token_json: Value = token_response.json().await.unwrap();
    let access_token = token_json["access_token"].as_str().unwrap();

    // Получение информации о пользователе
    let user_response = client
        .get("https://api.github.com/user")
        .header("Authorization", format!("Bearer {}", access_token))
        .header("User-Agent", "rust-oauth-app")
        .send()
        .await
        .unwrap();

    let user_info: Value = user_response.json().await.unwrap();
    let user_id = user_info["id"].to_string();
    let email = user_info["email"].as_str().unwrap_or("no-email@github.com");

    // Создаём JWT токен
    match crate::jwt::create_jwt(&user_id, email) {
        Ok(token) => HttpResponse::Ok().json(serde_json::json!({
            "token": token,
            "user": user_info
        })),
        Err(e) => HttpResponse::InternalServerError().body(format!("JWT error: {}", e)),
    }
}

4. Защита эндпоинтов с помощью JWT middleware

Теперь создадим middleware, который будет проверять JWT токен в заголовке Authorization.

// src/middleware.rs
use actix_web::{dev::ServiceRequest, Error, HttpMessage};
use actix_web::error::ErrorUnauthorized;
use crate::jwt::validate_jwt;

pub async fn jwt_middleware(req: ServiceRequest) -> Result<ServiceRequest, Error> {
    let auth_header                

Блоги

Рекомендаций по книгам