<h2>Fundamentos de JWT e Segurança em APIs</h2>
<p>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.</p>
<p>Em APIs REST modernas, o fluxo típico é: o cliente faz login, recebe um JWT, e envia esse token no header <code>Authorization: Bearer <token></code> 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.</p>
<h2>Configuração Inicial com Axum e Dependências</h2>
<p>Vamos começar adicionando as dependências necessárias ao <code>Cargo.toml</code>. Você precisa de <code>axum</code> para o framework, <code>jsonwebtoken</code> para manipular JWTs, <code>tokio</code> para runtime assíncrono, e <code>serde</code> para serialização.</p>
<pre><code class="language-toml">[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
jsonwebtoken = "9"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = "0.4"</code></pre>
<p>Agora, vamos criar a estrutura básica. Primeiro, definimos as estruturas para o payload do JWT e as credenciais de login:</p>
<pre><code class="language-rust">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,
}</code></pre>
<p>O campo <code>exp</code> define quando o token expira (em timestamp Unix). O <code>iat</code> marca quando foi emitido. Sempre defina expiração para limitar o tempo de vida do token — se roubado, terá tempo limitado de uso.</p>
<h2>Implementando Geração e Validação de JWT</h2>
<h3>Gerando Tokens</h3>
<p>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):</p>
<pre><code class="language-rust">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)
}</code></pre>
<h3>Validando Tokens</h3>
<p>Agora implementamos a função para validar e extrair dados do token:</p>
<pre><code class="language-rust">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)
}</code></pre>
<h2>Integrando com Axum e Criando Middleware</h2>
<h3>Extrator Customizado para JWT</h3>
<p>O Axum permite criar extractors que processam requisições automaticamente. Vamos criar um que valida o JWT:</p>
<pre><code class="language-rust">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(),
}
}
}</code></pre>
<h3>Rotas de Autenticação e Recursos Protegidos</h3>
<pre><code class="language-rust">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();
}</code></pre>
<p>Quando um cliente faz POST em <code>/login</code> com credenciais válidas, recebe um JWT. Ao acessar <code>/protected</code> com o header <code>Authorization: Bearer <token></code>, o extrator <code>AuthenticatedUser</code> valida automaticamente e passa as claims para o handler.</p>
<h2>Considerações de Segurança em Produção</h2>
<p>A chave secreta deve ser <strong>nunca</strong> 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.</p>
<p>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.</p>
<h2>Conclusão</h2>
<p>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.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.rs/axum/" target="_blank" rel="noopener noreferrer">Documentação oficial do Axum</a></li>
<li><a href="https://tools.ietf.org/html/rfc7519" target="_blank" rel="noopener noreferrer">RFC 7519 - JSON Web Token (JWT)</a></li>
<li><a href="https://docs.rs/jsonwebtoken/" target="_blank" rel="noopener noreferrer">Crate jsonwebtoken no Docs.rs</a></li>
<li><a href="https://tokio.rs/" target="_blank" rel="noopener noreferrer">Tokio async runtime</a></li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html" target="_blank" rel="noopener noreferrer">OWASP - Authentication Cheat Sheet</a></li>
</ul>