Login System

Build a login flow with brute-force protections and good UX.

On this page

Login Flow Overview

A safe login flow: validate input, fetch user by email, verify password hash, regenerate session ID, store minimal session state, and return a safe message. Never reveal whether the email exists.

Minimal DB Schema (Example)

Your real schema may differ, but the idea is: store email + password_hash + role.

-- users table (example)
-- id (int), email (varchar), password_hash (varchar), role (varchar), created_at (datetime)

Login Handler (POST)

Validate inputs, verify password, and start a session. Use a generic error message for all failures.

<?php
session_start();

$email = trim($_POST["email"] ?? "");
$password = $_POST["password"] ?? "";

if ($email === "" || $password === "") {
  echo "Invalid credentials";
  exit;
}

$stmt = $pdo->prepare("SELECT id, email, password_hash, role FROM users WHERE email = ? LIMIT 1");
$stmt->execute([$email]);

$user = $stmt->fetch();

if (!$user || !password_verify($password, $user["password_hash"])) {
  // optional: add delay or rate limiting (see below)
  echo "Invalid credentials";
  exit;
}

// prevent session fixation
session_regenerate_id(true);

$_SESSION["user_id"] = (int)$user["id"];
$_SESSION["role"] = $user["role"];

echo "OK";

Basic Brute-force Protection

At minimum, rate limit login attempts by IP/email. A quick baseline is to add a small delay on failure and log attempts. Stronger options: Redis rate limit, per-account lockouts, and monitoring.

<?php
// On failure (baseline):
usleep(200000); // 200ms delay (small friction)
error_log("Login failed for email: " . $email);

Production Tip

Use HTTPS, secure session cookies, generic error messages, session regeneration on login, and real rate limiting. Log failures (without storing raw passwords) and monitor suspicious patterns.