Bem-vindo à aula 68 do nosso curso de PHP! Hoje vamos explorar um dos tópicos mais importantes no desenvolvimento web moderno: autenticação com tokens, com foco no JSON Web Token (JWT). Em aplicações atuais, especialmente aquelas que usam APIs e arquiteturas de microsserviços, a autenticação stateless (sem estado) se tornou padrão, e o JWT é uma das tecnologias mais utilizadas para implementá-la.

Nesta aula, você não apenas entenderá o que é um JWT, mas também verá sua estrutura interna, quando faz sentido usá-lo, e quais cuidados de segurança são indispensáveis. Ao final, você terá uma base sólida para implementar autenticação JWT em seus projetos PHP, seja em uma API REST ou em um sistema web com SPA (Single Page Application).

O que é JWT

JWT (JSON Web Token) é um padrão aberto (RFC 7519) que define uma forma compacta e autônoma de transmitir informações entre partes como um objeto JSON. Essas informações podem ser verificadas e confiáveis porque são assinadas digitalmente. O JWT pode ser assinado usando um segredo (com o algoritmo HMAC) ou um par de chaves pública/privada (com RSA ou ECDSA).

Na prática, um JWT é uma string longa e codificada que contém três partes separadas por pontos. Ele é usado principalmente para autenticação e troca de informações seguras entre um cliente (como um navegador) e um servidor. Diferente de sessões tradicionais baseadas em cookies, o JWT é stateless: o servidor não precisa armazenar informações de sessão, pois o token contém as informações necessárias para autenticar o usuário.

Vamos ver um exemplo de um token JWT real:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Esse token é composto por três partes: header, payload e assinatura. Cada parte é codificada em Base64URL e separada por um ponto. Vamos detalhar cada uma delas na próxima seção.

Estrutura

Um JWT é dividido em três seções: Header, Payload e Signature, sempre nessa ordem, separadas por pontos. Vamos analisar cada uma.

Header

O header contém metadados sobre o token, como o tipo (JWT) e o algoritmo de assinatura usado (ex.: HS256, RS256). Um exemplo de header em JSON:

{
  "alg": "HS256",
  "typ": "JWT"
}

Esse objeto é codificado em Base64URL para formar a primeira parte do token.

Payload

O payload (ou claims) contém as declarações sobre o usuário e dados adicionais. Existem três tipos de claims: registrados, públicos e privados. Claims registrados são predefinidos como sub (subject), iat (issued at), exp (expiration), entre outros. Claims públicos podem ser definidos por quem usa o JWT, mas devem ser registrados no IANA. Claims privados são personalizados para a aplicação. Exemplo de payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Esse objeto também é codificado em Base64URL.

Assinatura

A assinatura é criada concatenando o header codificado, um ponto, o payload codificado, e aplicando o algoritmo de assinatura especificado no header, usando uma chave secreta (no caso de HMAC) ou chave privada (no caso de RSA). A assinatura garante que o token não foi alterado e, no caso de RSA, também autentica a origem.

Em PHP, você pode criar e verificar JWTs usando bibliotecas como firebase/php-jwt. Veja um exemplo de como gerar um token com essa biblioteca:

require_once 'vendor/autoload.php';

use Firebase\JWT\JWT;

$key = "sua_chave_secreta";
$payload = [
    'sub' => '1234567890',
    'name' => 'John Doe',
    'iat' => time(),
    'exp' => time() + 3600 // expira em 1 hora
];

$jwt = JWT::encode($payload, $key, 'HS256');
echo $jwt;
?>

Para decodificar e verificar:

require_once 'vendor/autoload.php';

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$key = "sua_chave_secreta";
$jwt = "token_recebido";

try {
    $decoded = JWT::decode($jwt, new Key($key, 'HS256'));
    print_r($decoded);
} catch (Exception $e) {
    echo 'Erro na verificação: ' . $e->getMessage();
}
?>

Quando usar

JWT é ideal para cenários onde você precisa de autenticação stateless e escalável, como APIs RESTful, aplicações SPA, e sistemas de microsserviços. Ao usar JWT, o servidor não precisa armazenar sessões, o que facilita a escalabilidade horizontal e a integração entre diferentes serviços.

Porém, JWT não é recomendado para todas as situações. Por exemplo, se sua aplicação é tradicional com renderização no servidor, sessões baseadas em cookies podem ser mais simples e seguras. JWT também pode ser problemático para gerenciar revogação de tokens, pois cada token é válido até expirar, a menos que você implemente uma lista negra.

Aqui estão alguns cenários comuns:

  • APIs públicas ou privadas: JWT é frequentemente usado para autenticar requisições a APIs, onde o cliente envia o token no cabeçalho Authorization.
  • SPAs: Aplicações de página única que se comunicam com um backend via AJAX podem usar JWT para manter o usuário autenticado.
  • Microserviços: Em uma arquitetura de microsserviços, JWT pode carregar informações do usuário para que cada serviço valide o token sem depender de um serviço central de autenticação.
  • Mobile: Aplicativos móveis podem armazenar tokens de forma segura e enviá-los nas requisições.

Em PHP, você pode integrar JWT em frameworks como Laravel, Symfony, ou em aplicações puras. O exemplo abaixo mostra como verificar um token em um endpoint simples usando PHP puro:

$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';

if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
    $token = $matches[1];
    // Verificar token com a biblioteca
} else {
    http_response_code(401);
    echo 'Token não fornecido';
    exit;
}
?>

Cuidados de segurança

Embora JWT seja seguro quando bem implementado, existem várias armadilhas que podem comprometer a segurança. Vamos listar as principais:

  • Use HTTPS: Sempre transmita tokens via HTTPS para evitar interceptação por terceiros. Nunca envie tokens em URLs ou em logs.
  • Armazene tokens com segurança: No cliente, evite armazenar tokens em localStorage se possível, pois isso é vulnerável a ataques XSS. Prefira cookies com atributo HttpOnly e Secure.
  • Defina expiração curta: Tokens com expiração longa aumentam o risco se forem comprometidos. Use expiração de 15 minutos a algumas horas, e use refresh tokens para renovar.
  • Valide sempre a assinatura: Nunca confie em um token sem verificar a assinatura. Use a chave correta e o algoritmo esperado.
  • Proteja a chave secreta: A chave usada para assinar tokens deve ser mantida em segredo absoluto. Em PHP, use variáveis de ambiente ou arquivos fora do diretório público.
  • Valide o alg: Evite aceitar tokens com algoritmo none ou algoritmos não suportados. Configure a biblioteca para aceitar apenas algoritmos específicos.
  • Cuidado com ataques de confusão de algoritmo: Em alguns casos, um atacante pode tentar trocar o algoritmo de RS256 para HS256, usando a chave pública como segredo. Verifique sempre o algoritmo esperado.
  • Implemente revogação: Se um usuário for deslogado ou sua conta for comprometida, você deve ter um mecanismo para invalidar tokens, como uma lista negra ou versão de token.

Vamos ver um exemplo de como verificar o algoritmo em PHP com a biblioteca firebase/php-jwt:

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$key = "sua_chave_secreta";
$token = "...";

try {
    $decoded = JWT::decode($token, new Key($key, 'HS256'));
    // Verificar se o algoritmo é HS256
    if ($decoded->alg !== 'HS256') {
        throw new Exception('Algoritmo inválido');
    }
} catch (Exception $e) {
    // Tratar erro
}
?>

Além disso, é importante lembrar que o JWT é assinado, não criptografado. Ou seja, os dados do payload podem ser lidos por qualquer pessoa que tenha o token. Portanto, nunca coloque informações sensíveis (como senhas) no payload.

Boas práticas e observações finais

Para finalizar, aqui vão algumas boas práticas ao trabalhar com JWT em PHP:

  • Use uma biblioteca consolidada como firebase/php-jwt ou lcobucci/jwt em vez de implementar sua própria solução.
  • Mantenha a chave secreta em um local seguro, como um arquivo .env fora do diretório público.
  • Sempre valide a expiração (exp) e o emissor (iss) se aplicável.
  • Considere usar refresh tokens para renovar o acesso sem forçar um novo login.
  • Monitore e registre tentativas de autenticação para detectar ataques.

Com isso, você está pronto para implementar autenticação JWT em seus projetos PHP. Lembre-se de que a segurança é um processo contínuo, e você deve sempre estar atento às melhores práticas e atualizações de segurança.

Referências

Exercícios

  1. Exercício 1: Explique com suas palavras o que é um JWT e por que ele é considerado "stateless".
  2. ✓ Resposta: JWT é um padrão para transmitir informações seguras entre partes, assinado digitalmente. Ele é stateless porque o servidor não precisa armazenar informações de sessão; todas as informações necessárias estão no próprio token, que é verificado a cada requisição.
  3. Exercício 2: Dado o seguinte payload, decodifique manualmente a parte do payload do token (não se preocupe com a assinatura): eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. Qual é o JSON resultante?
  4. ✓ Resposta: O payload decodificado é: {"sub":"1234567890","name":"John Doe","iat":1516239022}.
  5. Exercício 3: Escreva um código PHP usando a biblioteca firebase/php-jwt para gerar um token com expiração de 10 minutos, contendo o claim user_id com valor 42.
  6. ✓ Resposta:
    use Firebase\JWT\JWT;
    
    $key = 'sua_chave_secreta';
    $payload = [
        'user_id' => 42,
        'iat' => time(),
        'exp' => time() + 600 // 10 minutos
    ];
    
    $jwt = JWT::encode($payload, $key, 'HS256');
    echo $jwt;
  7. Exercício 4: Liste três cuidados de segurança essenciais ao usar JWT e explique cada um.
  8. ✓ Resposta: 1) Use HTTPS para evitar interceptação. 2) Armazene tokens de forma segura (ex.: cookies HttpOnly). 3) Valide sempre a assinatura e o algoritmo para evitar tokens forjados.
  9. Exercício 5: Em que cenários você NÃO recomendaria usar JWT? Dê um exemplo.
  10. ✓ Resposta: Não recomendaria JWT em aplicações simples com renderização no servidor e que precisam de revogação imediata de sessão, pois o JWT não pode ser facilmente revogado antes da expiração. Por exemplo, um sistema de administração onde você queira deslogar um usuário imediatamente se a conta for comprometida.