Suas ideias em realidade digital!
>
Em API, um login que “funciona” pode ser fácil de quebrar com brute force, vazamento de token ou senha fraca. Aqui você vai implementar um login seguro com:
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(190) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(30) NOT NULL DEFAULT 'user',
ustatus VARCHAR(20) NOT NULL DEFAULT 'ativo',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Na hora de registrar o usuário, gere o hash:
3) Rate limit: proteção contra brute force
Você pode fazer rate limit simples com banco (recomendado) ou arquivo/cache. Aqui vai um modelo com banco.
Tabela de tentativas
CREATE TABLE auth_attempts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
ip VARCHAR(64) NOT NULL,
email VARCHAR(190) NULL,
tries INT NOT NULL DEFAULT 0,
locked_until DATETIME NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_ip_email (ip, email)
);
Funções de rate limit
prepare("SELECT locked_until FROM auth_attempts WHERE ip=:ip AND email=:email LIMIT 1");
$stmt->execute([':ip' => $ip, ':email' => $email]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row && !empty($row['locked_until']) && strtotime($row['locked_until']) > time();
}
function registerFail(PDO $pdo, string $ip, ?string $email, int $maxTries = 5, int $lockMinutes = 10): void {
[$ip, $email] = authKey($ip, $email);
$stmt = $pdo->prepare("INSERT INTO auth_attempts (ip,email,tries) VALUES (:ip,:email,1)
ON DUPLICATE KEY UPDATE tries = tries+1");
$stmt->execute([':ip' => $ip, ':email' => $email]);
$stmt = $pdo->prepare("SELECT tries FROM auth_attempts WHERE ip=:ip AND email=:email LIMIT 1");
$stmt->execute([':ip' => $ip, ':email' => $email]);
$tries = (int)($stmt->fetch(PDO::FETCH_ASSOC)['tries'] ?? 0);
if ($tries >= $maxTries) {
$until = (new DateTimeImmutable())->modify("+{$lockMinutes} minutes")->format('Y-m-d H:i:s');
$pdo->prepare("UPDATE auth_attempts SET locked_until=:u WHERE ip=:ip AND email=:email")
->execute([':u' => $until, ':ip' => $ip, ':email' => $email]);
}
}
function resetAttempts(PDO $pdo, string $ip, ?string $email): void {
[$ip, $email] = authKey($ip, $email);
$pdo->prepare("DELETE FROM auth_attempts WHERE ip=:ip AND email=:email")
->execute([':ip' => $ip, ':email' => $email]);
}
4) Endpoint de login (email/senha) com JWT
Este exemplo:
- bloqueia login se estiver “locked”
- valida email/senha
- gera JWT
- zera tentativas ao acertar
'email e password são obrigatórios']);
exit;
}
if (isLocked($pdo, $ip, $email)) {
http_response_code(429);
echo json_encode(['error' => 'Muitas tentativas. Aguarde e tente novamente.']);
exit;
}
$stmt = $pdo->prepare("SELECT id, email, password_hash, role, ustatus FROM users WHERE email=:e LIMIT 1");
$stmt->execute([':e' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || $user['ustatus'] !== 'ativo' || !password_verify($pass, $user['password_hash'])) {
registerFail($pdo, $ip, $email, 5, 10);
http_response_code(401);
echo json_encode(['error' => 'Credenciais inválidas']);
exit;
}
// sucesso
resetAttempts($pdo, $ip, $email);
// access token (15 min)
$jwt = generateJWT([
'id' => (int)$user['id'],
'email'=> $user['email'],
'role' => $user['role']
], 'SEGREDO_SUPER_SECRETO', 900);
echo json_encode([
'token' => $jwt,
'token_type' => 'Bearer',
'expires_in' => 900
]);
5) Protegendo rotas (Bearer + validação)
'Token ausente']);
exit;
}
$payload = validateJWT($m[1], 'SEGREDO_SUPER_SECRETO');
if (!$payload) {
http_response_code(401);
echo json_encode(['error' => 'Token inválido/expirado']);
exit;
}
Boas práticas finais (produção)
- HTTPS obrigatório
- Rate limit por IP e usuário (como acima)
- JWT com expiração curta + refresh token (ideal)
- Não retornar mensagens que “entreguem” se o email existe
- Log de falhas de login (para auditoria)