web php
Authentication Basics
In a typical PHP web app, a user submits an email and password. The server finds the user record, verifies the submitted password against the stored password hash, regenerates the session ID, and stores the authenticated user ID in the session.
Passwords are stored as hashes
Never store plain-text passwords. Store the output of password_hash() and verify with password_verify().
<?php
declare(strict_types=1);
$plainPassword = 'correct horse battery staple';
$submittedPassword = 'correct horse battery staple';
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);
if (!password_verify($submittedPassword, $hash)) {
throw new RuntimeException('Invalid credentials.');
}
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
// Rehash after a successful login and save the new hash.
}
echo 'Password verified' . PHP_EOL;
// Prints:
// Password verified
PASSWORD_DEFAULT lets PHP choose a strong current default algorithm. Over time, password_needs_rehash() helps you upgrade stored hashes after successful login.
A small login decision
This example models the database row as an array so the flow is clear:
<?php
declare(strict_types=1);
/**
* @param array{ id: int, email: string, password_hash: string, active: bool } $user
*/
function authenticate(array $user, string $email, string $password): ?int
{
if (!$user['active']) {
return null;
}
if (!hash_equals($user['email'], $email)) {
return null;
}
if (!password_verify($password, $user['password_hash'])) {
return null;
}
return $user['id'];
}
$user = [
'id' => 42,
'email' => 'dev@example.com',
'password_hash' => password_hash('secret-password', PASSWORD_DEFAULT),
'active' => true,
];
$userId = authenticate($user, 'dev@example.com', 'secret-password');
echo $userId === null ? 'Invalid credentials' : 'Authenticated user ' . $userId;
// Output starts with:
// Authenticated user 42
Real login code should usually return a generic error such as "Invalid credentials" whether the email is unknown, the password is wrong, or the account is inactive. Specific errors can leak information about which accounts exist.
Marking the session as logged in
After successful authentication, regenerate the session ID before storing login state.
<?php
declare(strict_types=1);
$session = [];
$userId = 42;
$steps = ['regenerate session ID'];
$session['user_id'] = $userId;
$session['authenticated_at'] = time();
$steps[] = 'store authenticated user ID';
echo implode(' -> ', $steps) . PHP_EOL;
// Prints:
// regenerate session ID -> store authenticated user ID
In real request code, the first step is session_regenerate_id(true).
Login controls that matter
Authentication is more than password comparison. Production login systems commonly include:
- rate limiting or lockouts for repeated failures
- CSRF protection on the login form
- generic failure messages
- session regeneration after login
- secure session cookie settings
- audit logging for important account events
- password reset and email verification flows
- multi-factor authentication for sensitive systems
As a junior developer, you may not design all of these alone, but you should recognise them in real code and avoid weakening them.
Logout
Logout should clear session data, destroy the server-side session, and expire the session cookie. Unsetting only user_id can leave stale state behind.
Authentication is not authorization
Authentication proves who the user is. Authorization decides what that user is allowed to do. A logged-in user is not automatically allowed to edit every invoice, view every admin page, or delete every record.
What to check in a project
Check that passwords are hashed with password_hash() or a framework equivalent.
Check that password verification uses password_verify(), not direct string comparison.
Check that login regenerates the session ID before storing user_id.
Check that failure messages do not reveal whether the email exists.
Check that sensitive pages still perform authorization after authentication.
What you should be able to do
After this lesson, you should be able to explain authentication, verify password hashes, model a safe login flow, store login state in the session, and separate authentication from authorization.
Practice
Task: Model A Login Check
Write a small PHP script that authenticates a user record with an email and password.
Requirements
- Use
declare(strict_types=1);. - Store a password using
password_hash(). - Verify the submitted password using
password_verify(). - Return the user ID for a valid login.
- Return
nullfor an invalid login. - Include one valid password case and one invalid password case.
- Print the outcomes without printing the password hash.
Check Your Work
Run the script and confirm the valid login returns an ID while the invalid login does not.
Show solution
This solution models the user row as an array. A real application would load the row from a database.
<?php
declare(strict_types=1);
/**
* @param array{id: int, email: string, password_hash: string, active: bool} $user
*/
function loginUser(array $user, string $email, string $password): ?int
{
if (!$user['active'] || !hash_equals($user['email'], $email)) {
return null;
}
if (!password_verify($password, $user['password_hash'])) {
return null;
}
return $user['id'];
}
$user = [
'id' => 42,
'email' => 'dev@example.com',
'password_hash' => password_hash('secret-password', PASSWORD_DEFAULT),
'active' => true,
];
$valid = loginUser($user, 'dev@example.com', 'secret-password');
$invalid = loginUser($user, 'dev@example.com', 'wrong-password');
echo 'Valid login: ' . ($valid ?? 'failed') . PHP_EOL;
echo 'Invalid login: ' . ($invalid ?? 'failed') . PHP_EOL;
if ($valid !== null && password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
echo 'Password hash should be upgraded after this successful login.' . PHP_EOL;
}
// Prints:
// Valid login: 42
// Invalid login: failed
After a valid login in a web request, call session_regenerate_id(true) and store the user ID in the session.
Why This Works
The valid case proves the hash can be verified. The invalid case proves a bad password does not authenticate. The script never prints the hash or the submitted password.