Skip to content

Recipe Authentication

Muhammet Şafak edited this page May 29, 2026 · 1 revision

Recipe: Authentication & Fixation

Logging a user in is the most security-sensitive thing you'll do with sessions. The golden rule: regenerate the session id the moment the privilege level changes — i.e. right after you verify credentials. This defeats session fixation.

Uses: regenerateId, set/get, flush/destroy.

Bootstrap

use InitPHP\Sessions\Session;
use InitPHP\Sessions\Adapters\RedisAdapter;

Session::createImmutable(new RedisAdapter([
    'host' => '127.0.0.1',
    'port' => 6379,
    'ttl'  => 86400,
]))->start([
    'cookie_httponly' => true,
    'cookie_samesite' => 'Lax',
    'cookie_secure'   => !empty($_SERVER['HTTPS']),
]);

Login

function login(string $email, string $password): bool
{
    $user = findUserByEmail($email);

    if ($user === null || !password_verify($password, $user['password_hash'])) {
        return false;
    }

    // 1. Rotate the id — old fixated ids become useless.
    Session::regenerateId(true);

    // 2. Store the minimum needed to identify the user.
    Session::set('user_id', $user['id'])
        ->set('logged_in_at', time());

    return true;
}

Store an id, not the whole user record — reload the user per request from the id, so permission changes take effect immediately.

Reading the current user

function currentUserId(): ?int
{
    return Session::get('user_id');
}

function requireLogin(): void
{
    if (currentUserId() === null) {
        header('Location: /login');
        exit;
    }
}

Logout

Clear the data, rotate the id, and destroy the session:

function logout(): void
{
    Session::flush();            // clear all values
    Session::regenerateId(true); // new empty id, delete the old record
    Session::destroy();          // tear down
}

Optional: idle & absolute timeouts

const IDLE_TTL     = 1800;   // 30 min of inactivity
const ABSOLUTE_TTL = 28800;  // 8 h hard cap

function enforceTimeouts(): void
{
    $now  = time();
    $last = Session::get('last_seen', $now);
    $born = Session::get('logged_in_at', $now);

    if ($now - $last > IDLE_TTL || $now - $born > ABSOLUTE_TTL) {
        logout();
        return;
    }

    Session::set('last_seen', $now);
}

Optional: bind to client IP (PDO)

The PDO adapter can refuse to load a session from a different IP than created it:

new PDOAdapter(['pdo' => $pdo, 'table' => 'sessions', 'withIPAddress' => true]);

Use with care behind proxies and for mobile clients (see the adapter's warning).

Checklist

  • regenerateId(true) immediately after a successful login.
  • ✅ Store an id, reload the user each request.
  • cookie_httponly, cookie_secure (on HTTPS), and a sensible cookie_samesite.
  • flush() + regenerateId(true) + destroy() on logout.

See also

Clone this wiki locally