Authentication

By the end of this lesson you'll be able to register and log in users the secure way — hashing passwords with password_hash , checking them with password_verify , storing users with PDO prepared statements, and keeping people signed in with sessions.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Hashing a Password with password_hash

The golden rule of authentication: never store a password you can read back . Instead you hash it — run it through a one-way function that turns "hunter2" into a long scrambled string you can't reverse. PHP gives you exactly one function to do this well: password_hash() . It automatically adds a random salt (extra random data) so the same password produces a different hash every time, which kills entire classes of attack. You store that hash in your database; you never store the password itself.

Notice you never "decrypt" anything. To check a login you call password_verify($typed, $hash) : it re-hashes what the user typed using the salt baked into the stored hash and compares the two safely. Match means the password was right.

2️⃣ Choosing the Algorithm: BCRYPT & Argon2

PASSWORD_DEFAULT currently means bcrypt , a deliberately slow hash — slowness is a feature, because it makes brute-forcing expensive. You can raise the "cost" to make each guess cost more, or choose Argon2id ( PASSWORD_ARGON2ID ), the modern "memory-hard" winner of the Password Hashing Competition. Both are excellent; bcrypt is the safe default everywhere, Argon2id is preferred if your PHP build supports it.

Use PASSWORD_DEFAULT unless you have a reason not to — it lets PHP upgrade the algorithm for you in future versions, and (with the rehash check in section 6) your stored hashes upgrade along with it.

3️⃣ Registration: Storing Users with PDO

Registration is two steps: hash the password, then save the user. To save safely you use a prepared statement — you write the SQL with ? placeholders and pass the real values separately, so a malicious email like '; DROP TABLE users; -- is treated as plain data, never as SQL. This is your defence against SQL injection . Gluing user input straight into a query string is the classic beginner mistake; prepared statements make it impossible.

The stored row contains a bcrypt hash (it starts with $2y$ ), not the password. Even if your whole database leaked tomorrow, attackers still wouldn't have your users' passwords.

4️⃣ Login: Verify, Then Start a Session

Logging in means: look up the user by email, password_verify() their password against the stored hash, and if it matches, record that they're logged in. PHP tracks logged-in state with a session — a small server-side store keyed to a cookie. After session_start() you read and write $_SESSION like an array; here you save the user's id. Two security musts: return one generic failure for both "no such user" and "wrong password" (so you don't reveal which emails exist), and call session_regenerate_id(true) on success to stop session fixation (section covered in Common Errors).

5️⃣ Protecting Pages & Logging Out

Once login state lives in $_SESSION , protecting a page is easy: at the top, check for $_SESSION['user_id'] and, if it's missing, redirect to the login page and exit immediately — forgetting exit is a real bug, because the protected code below would still run. Logging out is the reverse: empty the session array, expire the session cookie, and destroy the session.

Now you try. The script below registers and logs in a user, but two function calls are missing. Fill in each ___ using the 👉 hint, then run it and check it against the Output panel.

One more. This time finish the prepared statement so the email is bound safely instead of pasted into the SQL.

6️⃣ "Remember Me" & Rehashing on Login

A normal session ends when the browser closes. A "remember me" feature keeps users signed in for weeks using a long-lived cookie — but never put the password or a plain user id in it. Instead generate a random token, store a hash of it in the database (treat it like a password), and send the token in an httponly , secure , samesite cookie. Separately, every successful login is a good moment to rehash : password_needs_rehash() tells you if your cost or algorithm has since been strengthened, so you can transparently upgrade the stored hash while you still have the plain password in hand.

📋 Quick Reference — PHP Authentication

No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments. This is the exact register-then-verify loop behind every real login screen.

Practice quiz

Which function should you use to hash a password for storage?

  • md5()
  • sha1()
  • password_hash()
  • base64_encode()

Answer: password_hash(). password_hash() uses a slow, salted, password-specific algorithm (bcrypt or Argon2). md5/sha1 are fast, unsalted, and unsafe for passwords.

How do you check a typed password against a stored hash?

  • password_verify($typed, $hash)
  • Decrypt the hash and compare
  • $typed === $hash
  • md5($typed) == $hash

Answer: password_verify($typed, $hash). Hashing is one-way; you never decrypt. password_verify() re-hashes the typed password with the stored salt and compares in constant time.

Why does hashing the same password twice give two different hashes?

  • The function is broken
  • PHP caches the first result
  • The cost factor changes randomly
  • password_hash() adds a random salt each time

Answer: password_hash() adds a random salt each time. Each call adds a random salt, so identical passwords produce different hashes — defeating rainbow tables. The salt is stored inside the hash.

What does PASSWORD_DEFAULT currently use as its algorithm?

  • md5
  • bcrypt
  • AES
  • ROT13

Answer: bcrypt. PASSWORD_DEFAULT currently means bcrypt (hashes start with $2y$). It can upgrade automatically in future PHP versions.

Why use PDO prepared statements when saving a user?

  • They keep user input out of the SQL, preventing SQL injection
  • They run faster than plain queries always
  • They hash the password automatically
  • They compress the database

Answer: They keep user input out of the SQL, preventing SQL injection. Prepared statements use ? or :name placeholders so values are bound as data, never as SQL — defeating injection like '; DROP TABLE users; --.

Where does PHP track that a user is logged in?

  • In a global $loggedIn variable
  • In the URL query string
  • In $_SESSION after session_start()
  • In a cookie holding the plaintext password

Answer: In $_SESSION after session_start(). After session_start() you read/write $_SESSION; here the user's id is stored to remember who is logged in.

Why call session_regenerate_id(true) right after a successful login?

  • To log the user out
  • To prevent session fixation by issuing a brand-new session id
  • To clear the database
  • To speed up the session

Answer: To prevent session fixation by issuing a brand-new session id. It issues a new session id and deletes the old one, so any id an attacker planted (session fixation) becomes useless.

What is a timing attack, and what protects you?

  • A DDoS on the login page — a firewall stops it
  • A slow database query — an index fixes it
  • An expired session — a longer TTL fixes it
  • Measuring comparison time to leak matches — password_verify/hash_equals compare in constant time

Answer: Measuring comparison time to leak matches — password_verify/hash_equals compare in constant time. Constant-time comparisons (password_verify, hash_equals) don't leak how many characters matched; never compare secrets with ==.

For a 'remember me' cookie, what should you store in the database?

  • The user's plaintext password
  • A hash of the random token (treated like a password)
  • The plain token exactly as sent to the browser
  • The user's email in plain text only

Answer: A hash of the random token (treated like a password). Store a hash of the random token (never the password, never a plain id), and set the cookie httponly, secure, and samesite.

What does password_needs_rehash() tell you?

  • Whether the password is correct
  • Whether the user exists
  • Whether the stored hash should be upgraded to a stronger cost/algorithm
  • Whether the session expired

Answer: Whether the stored hash should be upgraded to a stronger cost/algorithm. On a successful login it reports if your cost/algorithm was strengthened, so you can transparently re-store a stronger hash while you have the plain password.