How to Secure a PHP Application: A Complete Guide to Safe Authentication

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why "Rolling Your Own" Authentication is a Ticking Time Bomb

\n\n

Every second PHP developer has written their own login system at least once. It seems simple: a form, login verification, a session. But it is precisely at this "simple" stage that millions of websites perish. SQL injections, session theft, password brute-forcing — attacks that turn your project into a giveaway of personal data.

\n\n

In this article, we will break down secure authentication in PHP from scratch. No magic — only proven practices used at Facebook, GitHub, and your favorite framework. You will learn how to properly hash passwords, protect sessions from interception, and implement modern mechanisms like JWT.

\n\n

Ready? Let's go.

\n\n

1. Password Hashing: Why md5() is a Death Sentence

\n\n

The first rule of security: never, ever store passwords in plain text. The second rule: do not use outdated algorithms like MD5 or SHA1. They are calculated in microseconds, and any schoolkid with a laptop can crack your password in an hour.

\n\n

PHP has a built-in solution — the password_hash() and password_verify() functions. They use bcrypt (or Argon2, if PHP ≥ 7.2) and automatically add a "salt" — a random string that makes each hash unique.

\n\n

Example of Proper Hashing

\n
// User registration\n$password = $_POST['password'];\n$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);\n\n// Save $hash to the database\n// No manual salt! PHP handles it all.\n
\n\n

Password Verification on Login

\n
// User login\n$inputPassword = $_POST['password'];\n$storedHash = 'from the database';\n\nif (password_verify($inputPassword, $storedHash)) {\n    // Password is correct — let them in\n} else {\n    // Password is incorrect\n}\n
\n\n

Note the cost parameter. It sets the "cost" of computing the hash. The higher the number, the longer it takes an attacker to brute-force the password. For 2024, cost = 12 is the sweet spot between security and speed.

\n\n

2. Sessions: How Not to Hand Over the Keys to the House

\n\n

After a successful login, we usually start a session. But standard PHP sessions have weak points: predictable IDs, leakage via URL, session fixation.

\n\n

Secure Session Configuration

\n
// At the very beginning of the script, before any HTML output\nini_set('session.use_only_cookies', 1);\nini_set('session.use_strict_mode', 1);\nini_set('session.cookie_httponly', 1);\nini_set('session.cookie_secure', 1); // HTTPS only\nini_set('session.cookie_samesite', 'Lax');\n\nsession_start();\n\n// Regenerate session ID after login\nif ($login_success) {\n    session_regenerate_id(true);\n    $_SESSION['user_id'] = $user['id'];\n}\n
\n\n

What we did:

\n
    \n
  • use_only_cookies — forbid passing the session ID via URL (to prevent theft from a link)
  • \n
  • use_strict_mode — block uninitialized sessions
  • \n
  • httponly — prevent JavaScript access to the session cookie (XSS protection)
  • \n
  • secure — the cookie is transmitted only over HTTPS
  • \n
  • samesite — protection against CSRF attacks via cross-site request forgery
  • \n
\n\n

And most importantly: always regenerate the session ID after login. Otherwise, an attacker could preemptively give the user their own session ID and hijack the account.

\n\n

3. CSRF Protection: Verifying the Request Was Sent by You

\n\n

CSRF (Cross-Site Request Forgery) is an attack where a user clicks a link like /delete-account?id=1 and, without intending to, performs an action. To avoid this, use tokens.

\n\n

Generating and Verifying a CSRF Token

\n
// Token generation (at the start of a session or when loading a form)\nif (empty(                

Blogs

Book Recommendations