Autenticação com tokens (JWT, visão geral)
Esta aula apresenta uma visão geral sobre autenticação com tokens JWT em PHP, abordando sua estrutura, casos de uso e cuidados de segurança essenciais. O aluno aprenderá o que é JWT, como é composto, quando utilizá-lo e como evitar vulnerabilidades comuns, com exemplos práticos de implementação.
Nesta aula, vamos explorar a autenticação baseada em tokens, com foco no JWT (JSON Web Token). Você entenderá o que é JWT, sua estrutura interna, os cenários em que ele é vantajoso e as práticas de segurança que devem ser adotadas para evitar falhas. Este conhecimento é fundamental para construir APIs modernas e aplicações web com autenticação stateless.
O JWT é um padrão aberto (RFC 7519) que define um formato compacto e autocontido para transmitir informações entre partes de forma segura. Ele é amplamente utilizado em sistemas distribuídos, como APIs REST, para autenticação e troca de informações. Vamos analisar cada aspecto com exemplos práticos em PHP para que você possa aplicar esses conceitos em seus projetos.
O que é JWT
JWT (JSON Web Token) é um token de acesso baseado em JSON que permite transmitir dados entre duas partes de forma segura e compacta. Ele é composto por três partes: cabeçalho, payload e assinatura, codificadas em Base64Url e separadas por pontos. O token é assinado digitalmente, geralmente com um algoritmo HMAC ou RSA, garantindo que o conteúdo não foi alterado e que a origem é autêntica.
Em uma aplicação PHP, o JWT é frequentemente usado para autenticar usuários em APIs. Quando o usuário faz login, o servidor gera um token que o cliente armazena (geralmente em localStorage ou cookie) e envia nas requisições subsequentes no cabeçalho Authorization. O servidor valida o token a cada requisição, sem precisar manter sessões no servidor, o que torna a autenticação stateless e escalável.
Um exemplo típico de uso: um aplicativo mobile que acessa uma API PHP. O usuário envia credenciais, o servidor valida e retorna um JWT. O app envia esse token em cada requisição, e o servidor apenas verifica a assinatura e a expiração, sem consultar um banco de dados de sessões.
Estrutura
O JWT é composto por três seções: Header, Payload e Signature. Cada seção é codificada em Base64Url. O formato final é header.payload.signature. Vamos detalhar cada parte:
- Header: contém o tipo do token (JWT) e o algoritmo de assinatura (ex.: HS256). Exemplo:
{"alg":"HS256","typ":"JWT"}. - Payload: contém as claims (declarações), que são informações sobre o usuário e metadados. Existem claims registradas, como
exp(expiração),iat(emitido em),sub(sujeito), e claims personalizadas, comouser_id. - Signature: é gerada a partir da codificação do header e payload, combinada com um segredo (no caso de HMAC) ou chave privada (no caso de RSA). A assinatura garante a integridade do token.
Para gerar um JWT em PHP, usamos bibliotecas como firebase/php-jwt. Veja um exemplo de criação e validação:
123,
'username' => 'joao',
'iat' => time(),
'exp' => time() + 3600
];
// Gerar token
$jwt = JWT::encode($payload, $key, 'HS256');
echo $jwt;
// Validar token
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
print_r($decoded);
?>No exemplo, o token é gerado com expiração de 1 hora. Na validação, a biblioteca verifica a assinatura e a expiração automaticamente, lançando exceções se inválido.
Quando usar
O JWT é ideal para sistemas onde a autenticação precisa ser stateless, como APIs RESTful, aplicações de página única (SPA) e aplicativos móveis. Ele elimina a necessidade de armazenar sessões no servidor, facilitando a escalabilidade horizontal. Também é útil em microsserviços, onde um serviço pode validar o token sem consultar um serviço central de autenticação.
No entanto, não é recomendado para aplicações web tradicionais com sessões baseadas em cookies, onde você precisa de revogação imediata de acesso (ex.: logout em todos os dispositivos). Nesses casos, sessões server-side são mais simples. JWT também não é adequado para armazenar dados sensíveis, pois o payload é apenas codificado, não criptografado.
Exemplo prático de uso em API PHP:
// Login endpoint
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_GET['route'] === 'login') {
$user = getUserByCredentials($_POST['email'], $_POST['password']);
if ($user) {
$token = generateJWT($user);
echo json_encode(['token' => $token]);
exit;
}
}
// Protected endpoint
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_GET['route'] === 'profile') {
$headers = getallheaders();
if (!isset($headers['Authorization'])) {
http_response_code(401);
exit;
}
$token = str_replace('Bearer ', '', $headers['Authorization']);
try {
$user = validateJWT($token);
echo json_encode(['user' => $user]);
} catch (Exception $e) {
http_response_code(401);
echo 'Token inválido';
}
}Nesse fluxo, o cliente envia o token no cabeçalho Authorization como Bearer <token>. O servidor valida e responde com os dados do usuário.
Cuidados de segurança
A segurança do JWT depende de como ele é implementado. Aqui estão os principais cuidados:
- Use HTTPS sempre: o token deve ser transmitido apenas por conexões seguras para evitar interceptação.
- Escolha algoritmos fortes: prefira HS256 (HMAC com SHA-256) ou RS256 (RSA). Evite algoritmos como 'none' ou HS256 com chave fraca.
- Mantenha a chave secreta em local seguro: nunca exponha a chave em código público. Use variáveis de ambiente ou serviços de gerenciamento de segredos.
- Defina tempo de expiração curto: o token deve expirar em minutos ou horas, não dias. Para maior segurança, use refresh tokens.
- Não coloque dados sensíveis no payload: dados como senha, CPF, etc., não devem ser incluídos, pois o payload é apenas codificado, não criptografado.
- Valide todas as claims: além da assinatura, verifique
exp,aud(audiência) eiss(emissor) para evitar tokens reutilizados em outros contextos. - Proteja contra ataques CSRF: se você armazenar o token em cookie, configure a flag
SameSitee use tokens anti-CSRF.
Um exemplo de validação com verificação de claims em PHP:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
try {
$decoded = JWT::decode($token, new Key($key, 'HS256'));
// Verificar expiração
if ($decoded->exp < time()) {
throw new Exception('Token expirado');
}
// Verificar audiência
if ($decoded->aud !== 'seu-app') {
throw new Exception('Audiência inválida');
}
} catch (Exception $e) {
// Tratar erro
}Além disso, considere implementar lista de revogação (denylist) para tokens invalidados antes da expiração, e use refresh tokens para renovar o acesso sem exigir novo login.
Boas práticas e observações finais
Ao implementar JWT em PHP, utilize bibliotecas consolidadas como firebase/php-jwt ou lcobucci/jwt, em vez de codificar manualmente, pois elas já tratam de segurança e edge cases. Sempre armazene o token no lado do cliente de forma segura (prefira memória ou cookies httpOnly). Em APIs, o token deve ser enviado no cabeçalho Authorization, nunca em URLs. Por fim, mantenha a chave de assinatura rotacionada periodicamente e monitore logs de autenticação para detectar atividades suspeitas.
Referências
- RFC 7519 - JSON Web Token (JWT)
- Introduction to JWT - jwt.io
- firebase/php-jwt - GitHub
- PHP JSON Manual
- OWASP JWT Cheat Sheet
- MDN - HTTP Authentication
Exercícios
- Explique a diferença entre autenticação baseada em sessão e autenticação com JWT. Cite um cenário onde cada uma é mais adequada.✓ Resposta: A autenticação baseada em sessão armazena dados no servidor (ex.: em memória ou banco) e envia um ID de sessão ao cliente. JWT é stateless, pois o token contém as informações e é validado localmente. Sessão é adequada para aplicações web tradicionais com logout imediato e revogação. JWT é mais adequado para APIs e sistemas distribuídos, onde não se quer manter estado no servidor.
- Descreva as três partes de um JWT e o que cada uma contém.✓ Resposta: Header: contém o tipo do token (JWT) e o algoritmo de assinatura (ex.: HS256). Payload: contém claims como exp, iat, sub e dados personalizados. Signature: é gerada a partir do header e payload codificados com uma chave secreta, garantindo integridade.
- Escreva um código PHP que gere um JWT com expiração de 30 minutos usando a biblioteca firebase/php-jwt.✓ Resposta:
require_once 'vendor/autoload.php'; use Firebase\JWT\JWT; $key = 'minha-chave-secreta'; $payload = [ 'user_id' => 1, 'iat' => time(), 'exp' => time() + 1800 ]; $jwt = JWT::encode($payload, $key, 'HS256'); echo $jwt; - Quais são os principais cuidados de segurança ao usar JWT? Liste pelo menos 4.✓ Resposta: 1. Usar HTTPS para transmitir o token. 2. Escolher algoritmos fortes (HS256, RS256). 3. Manter a chave secreta em local seguro. 4. Definir expiração curta. 5. Não incluir dados sensíveis no payload. 6. Validar claims (exp, aud, iss).
- Escreva um código PHP que valide um token JWT e verifique se a claim 'exp' ainda é válida. Use a biblioteca firebase/php-jwt.✓ Resposta:
require_once 'vendor/autoload.php'; use Firebase\JWT\JWT; use Firebase\JWT\Key; $key = 'minha-chave-secreta'; $token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJpYXQiOjE3MTI5MTYwMDAsImV4cCI6MTcxMjkxNzgwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; try { $decoded = JWT::decode($token, new Key($key, 'HS256')); if ($decoded->exp < time()) { throw new Exception('Token expirado'); } echo "Token válido. Usuário ID: " . $decoded->user_id; } catch (Exception $e) { echo "Erro: " . $e->getMessage(); }