Skip to content

PHP Tutorial Authentication

Authentication Security

Lesson 290 of 369 3 min read By Deepak Paswan Updated Oct 5, 2026

A login that “works” is not a login that is safe. This lesson collects the habits that matter, with the reasons.

1. Hash passwords properly

password_hash() with PASSWORD_DEFAULT, password_verify() to check, password_needs_rehash() to upgrade. See Password Hashing.

2. Limit login attempts

Without a limit, an attacker can try thousands of passwords against one account, or one common password against thousands of accounts. Count failures and slow them down.

index.php
PHP
<?php
function loginIsBlocked(array $failures, int $now, int $max = 5, int $window = 900): bool
{
    $recent = array_filter($failures, fn(int $time) => $now - $time < $window);

    return count($recent) >= $max;
}

$now = 10000;

var_dump(loginIsBlocked([9900, 9910, 9920], $now));
var_dump(loginIsBlocked([9900, 9910, 9920, 9930, 9940], $now));
var_dump(loginIsBlocked([1000, 1010, 1020, 1030, 1040, 1050], $now));   // old failures
Output
OUTPUT
bool(false)
bool(true)
bool(false)

Store the failures in the database (or a cache such as Redis) by account and by IP address, and block for a time after too many. Be careful: locking an account for a long time lets an attacker lock out real users on purpose. A short, growing delay is friendlier than a permanent lock.

3. One generic error message

“Email or password is incorrect.” Never “unknown email” or “wrong password”. Make the timing equal, too (see password_verify()).

4. A sensible password policy

  • A minimum length of at least 8, better 10 to 12.
  • Allow long passphrases and all characters, including spaces.
  • Do not force composition rules (“one capital, one symbol”). They lead to Password1!.
  • Do not require regular password changes without a reason.
  • Check against lists of known leaked passwords (the Have I Been Pwned “range” API uses a k-anonymity method that never sends the full password).
  • Let users use a password manager: correct autocomplete attributes, no paste blocking.

5. HTTPS only

Without HTTPS, passwords and session cookies travel in the clear. Redirect HTTP to HTTPS, set the secure flag on cookies, and consider the HSTS header (see Security Headers).

6. Safe sessions

New session ID at login, secure cookie flags, idle and absolute timeouts, destroy on logout. See Secure Session Practices.

7. CSRF protection on every state-changing form

Login, logout, password change, delete. See CSRF.

8. Two-factor authentication (2FA)

A second proof, such as a code from an authenticator app (TOTP), protects an account even if the password leaks. Use a maintained library for TOTP rather than writing it yourself. Offer backup codes so a lost phone does not lock users out forever.

9. Protect sensitive actions

For changing the email or password, or deleting the account, ask for the current password again, even when the user is logged in.

10. Log security events, safely

Log failed logins, password changes and resets with the time, the user id and the IP address. Never log passwords or tokens. Review the log, or alert on unusual patterns.

index.php
PHP
<?php
function securityLog(string $event, array $context = []): string
{
    unset($context["password"], $context["token"]);

    return date("Y-m-d") . " " . $event . " " . json_encode($context);
}

echo securityLog("login_failed", ["email" => "ana@example.com", "password" => "oops", "ip" => "203.0.113.9"]);
Output
OUTPUT
2026-10-05 login_failed {"email":"ana@example.com","ip":"203.0.113.9"}

(The date will show today’s date.)

11. Keep secrets out of code

Database passwords, API keys and mail credentials come from environment variables or a config file outside the web root.

12. Update everything

An outdated PHP version, framework or plugin is how many sites get hacked. Update regularly and run composer audit.

Quick checklist

  • [ ] password_hash() / password_verify()
  • [ ] Rate limiting and slow-down on login, reset and registration
  • [ ] One generic error message, equal timing
  • [ ] HTTPS, secure and HttpOnly cookies
  • [ ] New session id at login, timeouts, full logout
  • [ ] CSRF tokens
  • [ ] Reset tokens: random, hashed, expiring, one-use
  • [ ] 2FA offered for important accounts
  • [ ] Logs without secrets
  • [ ] Updates
info
Why this matters in WordPress and Laravel
WordPress has no login limit by default, which is why security plugins add one. Laravel has a built-in rate limiter you can put on the login route. Knowing why these exist helps you configure them properly.