The Password Reset API provides a secure, token-based password recovery system. When a user requests a password reset, the API generates a unique token, stores it in the database with an expiration time, and sends a branded email with a reset link.
This API implements rate limiting (3 requests per 15 minutes per email) to prevent abuse.
POST /api/forgot_password.phpContent-Type: application/x-www-form-urlencodedemail=[email protected]
Initiates the password reset process by generating a secure token and sending a recovery email. For security reasons, the API always returns a success message regardless of whether the email exists.
The API never reveals whether an email exists in the database:
if (!$usuario) { // Same response whether email exists or not echo json_encode([ 'ok' => true, 'msg' => 'Si el correo está registrado, recibirás un enlace...' ]); exit;}
This prevents attackers from using the API to enumerate valid email addresses.
The API always uses the production URL for reset links, even in local development. This ensures emails sent from staging/dev environments work correctly.
<?php// In reset_password.php$token = $_GET['token'] ?? '';if (empty($token)) { die('Token no proporcionado');}// Verify token is valid and not expired$stmt = $pdo->prepare(" SELECT email FROM password_resets WHERE token = ? AND usado = 0 AND expira > UTC_TIMESTAMP() LIMIT 1");$stmt->execute([$token]);$reset = $stmt->fetch();if (!$reset) { die('Token inválido o expirado');}// Token is valid, show password reset form$email = $reset['email'];?>
<?php// After user submits new password$token = $_POST['token'];$newPassword = $_POST['password'];// Validate password (minimum 8 characters, etc.)if (strlen($newPassword) < 8) { die('La contraseña debe tener al menos 8 caracteres');}// Verify token again$stmt = $pdo->prepare(" SELECT email FROM password_resets WHERE token = ? AND usado = 0 AND expira > UTC_TIMESTAMP()");$stmt->execute([$token]);$reset = $stmt->fetch();if (!$reset) { die('Token inválido o expirado');}// Update password$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);$stmt = $pdo->prepare("UPDATE usuarios SET password = ? WHERE email = ?");$stmt->execute([$hashedPassword, $reset['email']]);// Mark token as used$stmt = $pdo->prepare("UPDATE password_resets SET usado = 1 WHERE token = ?");$stmt->execute([$token]);// Successecho 'Contraseña actualizada correctamente';?>
CREATE TABLE password_resets ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) NOT NULL, token VARCHAR(64) NOT NULL UNIQUE, expira DATETIME NOT NULL, usado TINYINT(1) DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_token (token), INDEX idx_email (email)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
The table is automatically created if it doesn’t exist when the API is first called.
# .env fileAPP_URL=https://computecnicos.comAPP_ENV=production # or 'development'# Brevo API (optional)BREVO_API_KEY=xkeysib-your-api-key-hereBREVO_SENDER_EMAIL=[email protected]BREVO_SENDER_NAME=Computécnicos
-- Manually expire a token for testingUPDATE password_resets SET expira = DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 HOUR)WHERE token = 'your-token-here';-- Verify token is now expiredSELECT *, (expira > UTC_TIMESTAMP()) as es_valido FROM password_resets WHERE token = 'your-token-here';