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 example
<?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 example
<?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 example
<?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 null for 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 example
<?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.