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, como user_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) e iss (emissor) para evitar tokens reutilizados em outros contextos.
  • Proteja contra ataques CSRF: se você armazenar o token em cookie, configure a flag SameSite e 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

Exercícios

  1. 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.
  2. 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.
  3. 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;
  4. 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).
  5. 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();
    }