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.
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 │ │ ) │ └─────────────────────────────────────────────────────────────────┘
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
]);
?>
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
}
};