Autenticação com JWT em PHP puro
1. Fundamentos do JWT e sua estrutura
JWT (JSON Web Token) é um padrão aberto (RFC 7519) que define uma forma compacta e autossuficiente de transmitir informações entre partes como um objeto JSON. Em APIs REST, ele é amplamente utilizado para autenticação stateless, onde o servidor não precisa armazenar sessões.
Um token JWT é composto por três partes separadas por pontos (.):
- Header: contém o tipo do token (
typ) e o algoritmo de assinatura (alg), geralmenteHS256para HMAC-SHA256. - Payload: contém as claims (declarações), como
sub(subject),iat(issued at),exp(expiration) e dados personalizados. - Signature: assinatura gerada a partir do header e payload codificados, usando uma chave secreta.
É importante distinguir: JWT refere-se ao token em si, JWS (JSON Web Signature) é o mecanismo de assinatura (o mais comum), e JWE (JSON Web Encryption) é a versão criptografada. Neste artigo, trabalharemos com JWS.
2. Preparação do ambiente e funções auxiliares
Primeiro, configure os headers HTTP e funções auxiliares para manipulação de JSON e constantes:
<?php
// config.php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Authorization, Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
define('JWT_SECRET', 'sua-chave-secreta-muito-forte-aqui');
define('JWT_EXPIRATION', 3600); // 1 hora em segundos
function jsonResposta($dados, $status = 200) {
http_response_code($status);
echo json_encode($dados, JSON_UNESCAPED_UNICODE);
exit();
}
3. Codificação manual do JWT (Header e Payload)
Implementaremos o Base64url encode, que substitui + por -, / por _ e remove =:
<?php
function base64urlEncode($dados) {
return rtrim(strtr(base64_encode($dados), '+/', '-_'), '=');
}
function base64urlDecode($dados) {
return base64_decode(strtr($dados, '-_', '+/'));
}
Agora, crie o header e payload do JWT:
<?php
function criarHeaderJWT() {
return json_encode([
'typ' => 'JWT',
'alg' => 'HS256'
]);
}
function criarPayloadJWT($dadosUsuario) {
$agora = time();
return json_encode([
'iss' => 'meu-sistema', // emissor
'sub' => $dadosUsuario['id'], // identificador do usuário
'iat' => $agora, // emitido em
'exp' => $agora + JWT_EXPIRATION, // expiração
'nome' => $dadosUsuario['nome'],
'email' => $dadosUsuario['email']
]);
}
4. Assinatura HMAC-SHA256 e geração do token
A assinatura é gerada com hash_hmac usando SHA256. A função completa para criar o token:
<?php
function criarJWT($dadosUsuario, $secreto = JWT_SECRET) {
$header = criarHeaderJWT();
$payload = criarPayloadJWT($dadosUsuario);
$headerEncoded = base64urlEncode($header);
$payloadEncoded = base64urlEncode($payload);
$assinatura = hash_hmac('sha256', "$headerEncoded.$payloadEncoded", $secreto, true);
$assinaturaEncoded = base64urlEncode($assinatura);
return "$headerEncoded.$payloadEncoded.$assinaturaEncoded";
}
Exemplo de uso:
<?php
$usuario = ['id' => 1, 'nome' => 'João Silva', 'email' => 'joao@email.com'];
$token = criarJWT($usuario);
echo $token; // eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOi...
5. Validação e verificação do token recebido
Para validar, extraímos o token do header Authorization: Bearer, separamos as partes e verificamos assinatura e expiração:
<?php
function extrairToken() {
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? '';
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
return $matches[1];
}
return null;
}
function validarJWT($token, $secreto = JWT_SECRET) {
$partes = explode('.', $token);
if (count($partes) !== 3) {
return ['valido' => false, 'erro' => 'Token malformado'];
}
[$headerEncoded, $payloadEncoded, $assinaturaEncoded] = $partes;
// Verificar assinatura
$assinaturaCalculada = hash_hmac('sha256', "$headerEncoded.$payloadEncoded", $secreto, true);
$assinaturaRecebida = base64urlDecode($assinaturaEncoded);
// Proteção contra timing attack
if (!hash_equals($assinaturaCalculada, $assinaturaRecebida)) {
return ['valido' => false, 'erro' => 'Assinatura inválida'];
}
// Decodificar payload
$payload = json_decode(base64urlDecode($payloadEncoded), true);
if (!$payload) {
return ['valido' => false, 'erro' => 'Payload inválido'];
}
// Verificar expiração
if (isset($payload['exp']) && $payload['exp'] < time()) {
return ['valido' => false, 'erro' => 'Token expirado'];
}
return ['valido' => true, 'payload' => $payload];
}
6. Integração com autenticação de usuário
Vamos simular um banco de dados com array estático e criar rotas de login e middleware de proteção:
<?php
// Simulação de banco de dados
$usuarios = [
1 => ['id' => 1, 'nome' => 'João Silva', 'email' => 'joao@email.com', 'senha' => password_hash('123456', PASSWORD_DEFAULT)],
2 => ['id' => 2, 'nome' => 'Maria Souza', 'email' => 'maria@email.com', 'senha' => password_hash('654321', PASSWORD_DEFAULT)]
];
// Rota de login
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/login') {
$dados = json_decode(file_get_contents('php://input'), true);
$email = $dados['email'] ?? '';
$senha = $dados['senha'] ?? '';
$usuarioEncontrado = null;
foreach ($usuarios as $u) {
if ($u['email'] === $email && password_verify($senha, $u['senha'])) {
$usuarioEncontrado = $u;
break;
}
}
if (!$usuarioEncontrado) {
jsonResposta(['erro' => 'Credenciais inválidas'], 401);
}
$token = criarJWT($usuarioEncontrado);
jsonResposta(['token' => $token, 'usuario' => ['id' => $usuarioEncontrado['id'], 'nome' => $usuarioEncontrado['nome']]]);
}
// Middleware de proteção
function middlewareAutenticacao() {
$token = extrairToken();
if (!$token) {
jsonResposta(['erro' => 'Token não fornecido'], 401);
}
$resultado = validarJWT($token);
if (!$resultado['valido']) {
jsonResposta(['erro' => $resultado['erro']], 401);
}
return $resultado['payload'];
}
// Rota protegida (exemplo)
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_SERVER['REQUEST_URI'] === '/perfil') {
$usuario = middlewareAutenticacao();
jsonResposta(['usuario' => $usuario]);
}
7. Tratamento de erros e boas práticas de segurança
Implemente mensagens de erro padronizadas e proteções adicionais:
<?php
function jsonErro($mensagem, $codigo = 400) {
$erros = [
'token_invalido' => 'Token inválido ou malformado',
'token_expirado' => 'Token expirado, faça login novamente',
'token_ausente' => 'Token de autenticação não fornecido',
'credenciais_invalidas' => 'Email ou senha incorretos'
];
$mensagemFormatada = $erros[$mensagem] ?? $mensagem;
jsonResposta(['erro' => $mensagemFormatada, 'codigo' => $mensagem], $codigo);
}
Boas práticas adicionais:
- Timing attack: Use
hash_equals()para comparação de assinaturas (já implementado acima). - Renovação de token: Implemente um token de refresh com expiração maior (ex: 7 dias) e um endpoint
/refreshque valida o refresh token e emite um novo access token. - Blacklist de tokens: Para logout ou revogação, mantenha uma lista (Redis ou banco) de tokens invalidados até sua expiração natural.
- HTTPS obrigatório: Nunca transmita tokens por HTTP simples.
- Payload mínimo: Inclua apenas dados essenciais no payload; informações sensíveis devem ser buscadas no servidor.
Exemplo de endpoint de refresh:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/refresh') {
$dados = json_decode(file_get_contents('php://input'), true);
$refreshToken = $dados['refresh_token'] ?? '';
// Validar refresh token (com expiração maior)
$resultado = validarJWT($refreshToken, JWT_SECRET_REFRESH);
if (!$resultado['valido']) {
jsonResposta(['erro' => 'Refresh token inválido'], 401);
}
// Emitir novo access token
$novoToken = criarJWT($resultado['payload']);
jsonResposta(['token' => $novoToken]);
}
Referências
- RFC 7519 - JSON Web Token — Especificação oficial do padrão JWT pelo IETF
- PHP: hash_hmac - Manual — Documentação oficial da função de hash HMAC no PHP
- PHP: hash_equals - Manual — Documentação oficial para comparação segura contra timing attack
- JWT.io - JSON Web Tokens — Ferramenta online para decodificar e verificar tokens JWT, com suporte a múltiplas linguagens
- OWASP - JSON Web Token Cheat Sheet — Guia de segurança da OWASP com boas práticas para implementação de JWT (aplicável a qualquer linguagem)