Fundamentos de JWT e Segurança em APIs
JSON Web Token (JWT) é um padrão aberto (RFC 7519) que define uma forma compacta e auto-contida de transmitir informações entre partes de forma segura. Um JWT é composto por três partes separadas por pontos: header (tipo de token e algoritmo), payload (dados do usuário) e signature (assinatura criptográfica). A grande vantagem é que o token é stateless — o servidor não precisa armazenar sessões, apenas verificar a assinatura.
Em APIs REST modernas, o fluxo típico é: o cliente faz login, recebe um JWT, e envia esse token no header Authorization: Bearer <token> em requisições subsequentes. O servidor valida a assinatura antes de processar a requisição. Isso elimina a necessidade de consultar um banco de dados a cada requisição, melhorando significativamente a performance. Rust com Axum é uma escolha excelente para isso: Axum é um web framework moderno, assíncrono e com sistema de tipos robusto que facilita implementar camadas de autenticação seguras.
Configuração Inicial com Axum e Dependências
Vamos começar adicionando as dependências necessárias ao Cargo.toml. Você precisa de axum para o framework, jsonwebtoken para manipular JWTs, tokio para runtime assíncrono, e serde para serialização.
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
jsonwebtoken = "9"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = "0.4"
Agora, vamos criar a estrutura básica. Primeiro, definimos as estruturas para o payload do JWT e as credenciais de login:
use serde::{Deserialize, Serialize};
use chrono::{Utc, Duration};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String, // subject (user id)
pub exp: i64, // expiration time
pub iat: i64, // issued at
pub email: String,
}
#[derive(Deserialize)]
pub struct LoginRequest {
pub email: String,
pub password: String,
}
#[derive(Serialize)]
pub struct LoginResponse {
pub token: String,
}
O campo exp define quando o token expira (em timestamp Unix). O iat marca quando foi emitido. Sempre defina expiração para limitar o tempo de vida do token — se roubado, terá tempo limitado de uso.
Implementando Geração e Validação de JWT
Gerando Tokens
Crie uma função que gera o JWT após validar as credenciais. Para simplicidade, vamos usar uma senha hardcoded (em produção, compare com hash bcrypt):
const SECRET_KEY: &[u8] = b"sua_chave_secreta_super_segura_min_32_bytes";
pub fn generate_token(email: String, user_id: String) -> Result<String, jsonwebtoken::errors::Error> {
let now = Utc::now();
let claims = Claims {
sub: user_id,
email,
iat: now.timestamp(),
exp: (now + Duration::hours(24)).timestamp(),
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(SECRET_KEY),
)?;
Ok(token)
}
Validando Tokens
Agora implementamos a função para validar e extrair dados do token:
pub fn validate_token(token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
decode::<Claims>(
token,
&DecodingKey::from_secret(SECRET_KEY),
&Validation::default(),
)
.map(|data| data.claims)
}
Integrando com Axum e Criando Middleware
Extrator Customizado para JWT
O Axum permite criar extractors que processam requisições automaticamente. Vamos criar um que valida o JWT:
use axum::{
async_trait,
extract::FromRequestParts,
http::request::Parts,
response::{IntoResponse, Response},
Json,
};
pub struct AuthenticatedUser(pub Claims);
#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
S: Send + Sync,
{
type Rejection = JsonResponse;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let header = parts
.headers
.get("authorization")
.and_then(|h| h.to_str().ok())
.ok_or_else(|| JsonResponse::Unauthorized)?;
let token = header
.strip_prefix("Bearer ")
.ok_or_else(|| JsonResponse::Unauthorized)?;
let claims = validate_token(token)
.map_err(|_| JsonResponse::Unauthorized)?;
Ok(AuthenticatedUser(claims))
}
}
pub enum JsonResponse {
Unauthorized,
}
impl IntoResponse for JsonResponse {
fn into_response(self) -> Response {
match self {
JsonResponse::Unauthorized => (
axum::http::StatusCode::UNAUTHORIZED,
Json(serde_json::json!({"error": "Unauthorized"})),
).into_response(),
}
}
}
Rotas de Autenticação e Recursos Protegidos
use axum::{routing::{post, get}, Router};
async fn login(
Json(payload): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, String> {
// Validação simples (em produção, use bcrypt)
if payload.password != "senha_correta" {
return Err("Invalid credentials".to_string());
}
let token = generate_token(payload.email.clone(), "user_123".to_string())
.map_err(|_| "Failed to generate token".to_string())?;
Ok(Json(LoginResponse { token }))
}
async fn protected_route(
AuthenticatedUser(claims): AuthenticatedUser,
) -> Json<serde_json::Value> {
Json(serde_json::json!({
"message": format!("Bem-vindo, {}!", claims.email),
"user_id": claims.sub
}))
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/login", post(login))
.route("/protected", get(protected_route));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
Quando um cliente faz POST em /login com credenciais válidas, recebe um JWT. Ao acessar /protected com o header Authorization: Bearer <token>, o extrator AuthenticatedUser valida automaticamente e passa as claims para o handler.
Considerações de Segurança em Produção
A chave secreta deve ser nunca hardcoded. Use variáveis de ambiente ou um vault de segurança. Implementar refresh tokens com tempo de vida curto também é crucial: o access token expira em minutos, e um refresh token (armazenado com segurança) permite obter novos access tokens sem fazer login novamente.
Sempre use HTTPS em produção, configure CORS apropriadamente se sua API é consumida por navegadores, e considere adicionar rate limiting na rota de login contra ataques de força bruta. Valide o formato do token antes de decodificar, e registre tentativas de acesso não autorizado para monitoramento de segurança.
Conclusão
Você aprendeu a implementar autenticação JWT em Rust com Axum de forma prática. Os pontos principais foram: (1) JWT é um padrão stateless e seguro ideal para APIs modernas, (2) Axum fornece extractors que facilitam validar tokens automaticamente em requisições, e (3) em produção, gerenciar secrets, implementar refresh tokens e usar HTTPS são obrigatórios. O código aqui é um ponto de partida sólido que você pode expandir com reset de senha, rate limiting e persistência real de usuários.