verified_user 4. Autenticação com JWT (JSON Web Tokens)

JWT é o padrão moderno para autenticação stateless em APIs REST. Diferente de sessões tradicionais (que exigem armazenamento no servidor), o JWT é um token auto-contido que carrega todas as informações necessárias para validar um usuário, assinado criptograficamente para prevenir adulteração.

Quando o usuário faz login, o backend gera um JWT contendo o ID do usuário, permissões e data de expiração. Este token é enviado ao frontend, que o armazena (geralmente em localStorage) e o inclui em todas as requisições subsequentes no header Authorization.

O backend valida o token verificando sua assinatura e data de expiração. Se válido, processa a requisição. Se expirado ou adulterado, retorna 401 Unauthorized. Este processo elimina a necessidade de banco de dados de sessões e escala perfeitamente em ambientes distribuídos.

info
📚 Conceito Chave: JWT não é criptografia! É uma assinatura digital. O conteúdo é visível (Base64), mas não pode ser alterado sem invalidar a assinatura. NUNCA armazene senhas ou dados sensíveis no payload.

architecture Anatomia de um JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

┌─────────────────────────────────────────────────────────────────┐
│ HEADER (Base64)                                              │
│ {                                                               │
│   "alg": "HS256",    ← Algoritmo de assinatura                │
│   "typ": "JWT"       ← Tipo do token                          │
│ }                                                               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ PAYLOAD (Base64) - Dados do usuário                        │
│ {                                                               │
│   "sub": "1234567890",  ← User ID                             │
│   "name": "John Doe",   ← Informações públicas                │
│   "iat": 1516239022,    ← Issued At (timestamp)               │
│   "exp": 1516242622     ← Expiration (timestamp)              │
│ }                                                               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ SIGNATURE (HMAC SHA256)                                     │
│ HMACSHA256(                                                     │
│   base64UrlEncode(header) + "." + base64UrlEncode(payload),   │
│   secret_key                                                    │
│ )                                                               │
└─────────────────────────────────────────────────────────────────┘
lock
🔐 Segurança Crítica: A secret_key deve ser uma string aleatória de no mínimo 256 bits (32 caracteres). Armazene em .env e NUNCA exponha no Git ou código frontend.

code Implementando JWT no PHP

📦 Instalação da Biblioteca (Firebase JWT)

# Via Composer (recomendado)
composer require firebase/php-jwt

# Ou baixar manualmente de:
# https://github.com/firebase/php-jwt

🔑 Gerando um Token JWT

 $issuedAt,         // Issued at
        'exp' => $expirationTime,   // Expiration
        'sub' => $userId,           // Subject (user ID)
        'data' => $extraData        // Dados customizados
    ];
    
    return JWT::encode($payload, JWT_SECRET, JWT_ALGORITHM);
}

/**
 * Valida e decodifica um JWT
 * @param string $token - Token JWT
 * @return object|false - Payload decodificado ou false se inválido
 */
function validateJWT($token) {
    try {
        $decoded = JWT::decode($token, new Key(JWT_SECRET, JWT_ALGORITHM));
        return $decoded;
    } catch (Exception $e) {
        error_log("JWT Validation Error: " . $e->getMessage());
        return false;
    }
}
?>

login Endpoint de Login Completo

prepare('
        SELECT id, email, password_hash, role 
        FROM users 
        WHERE email = ? AND active = 1
    ');
    $stmt->execute([$email]);
    $user = $stmt->fetch();
    
    // 4. Validar credenciais
    if (!$user || !password_verify($data['password'], $user['password_hash'])) {
        http_response_code(401);
        echo json_encode([
            'success' => false,
            'message' => 'Credenciais inválidas'
        ]);
        exit;
    }
    
    // 5. Gerar JWT
    $token = generateJWT($user['id'], [
        'email' => $user['email'],
        'role' => $user['role']
    ]);
    
    // 6. Atualizar last_login
    $stmt = $pdo->prepare('UPDATE users SET last_login = NOW() WHERE id = ?');
    $stmt->execute([$user['id']]);
    
    // 7. Retornar resposta
    echo json_encode([
        'success' => true,
        'token' => $token,
        'user' => [
            'id' => $user['id'],
            'email' => $user['email'],
            'role' => $user['role']
        ]
    ]);
    
} catch (InvalidArgumentException $e) {
    http_response_code(400);
    echo json_encode(['success' => false, 'message' => $e->getMessage()]);
} catch (Exception $e) {
    error_log("Login Error: " . $e->getMessage());
    http_response_code(500);
    echo json_encode(['success' => false, 'message' => 'Erro no servidor']);
}
?>

refresh Refresh Tokens: Prolongando a Sessão

access_time Access Token

  • Curta duração (15min - 1h)
  • Usado em cada requisição
  • Armazenado em memória/localStorage
  • Se roubado, expira rapidamente

autorenew Refresh Token

  • Longa duração (7-30 dias)
  • Usado apenas para gerar novos Access Tokens
  • Armazenado em httpOnly cookie
  • Pode ser revogado no banco
 false, 'message' => 'Refresh token não fornecido']);
    exit;
}

// 2. Validar refresh token no banco
$stmt = $pdo->prepare('
    SELECT user_id, expires_at 
    FROM refresh_tokens 
    WHERE token = ? AND revoked = 0
');
$stmt->execute([$refreshToken]);
$tokenData = $stmt->fetch();

if (!$tokenData || strtotime($tokenData['expires_at']) < time()) {
    http_response_code(401);
    echo json_encode(['success' => false, 'message' => 'Refresh token inválido ou expirado']);
    exit;
}

// 3. Gerar novo access token
$newAccessToken = generateJWT($tokenData['user_id']);

// 4. Retornar
echo json_encode([
    'success' => true,
    'token' => $newAccessToken
]);
?>
warning
⚠️ Importante: Refresh tokens devem ser armazenados no banco de dados com flag revoked para permitir logout forçado. Access tokens não podem ser revogados (são stateless).

integration_instructions Integrando com React

// frontend/src/services/authService.js
import axios from 'axios';

const API_URL = import.meta.env.VITE_API_URL;

// Configurar interceptor para incluir token em todas as requisições
axios.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Interceptor para refresh automático
axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    
    // Se 401 e não é retry
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        // Tentar refresh
        const { data } = await axios.post(`${API_URL}/auth/refresh`);
        localStorage.setItem('token', data.token);
        
        // Retentar requisição original
        originalRequest.headers.Authorization = `Bearer ${data.token}`;
        return axios(originalRequest);
      } catch (refreshError) {
        // Refresh falhou, fazer logout
        localStorage.removeItem('token');
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

export const authService = {
  async login(email, password) {
    const { data } = await axios.post(`${API_URL}/auth/login`, { email, password });
    localStorage.setItem('token', data.token);
    return data.user;
  },
  
  logout() {
    localStorage.removeItem('token');
    // Opcional: chamar endpoint de revoke
  }
};
check_circle
✅ Próxima Seção: Agora que temos autenticação JWT funcionando, vamos implementar Hash de Senhas com bcrypt para armazenar credenciais de forma segura.