ZetCode

PHP Sessions and Cookies

last modified October 22, 2026

In this article, we show how to keep state between HTTP requests in PHP with cookies and sessions.

HTTP is stateless: the server does not remember anything about a client between two requests. A cookie is a small piece of data the server asks the browser to store and send back with every later request.

A session keeps the data on the server and gives the browser only an identifier, stored in a cookie named PHPSESSID by default. Sensitive data, such as the logged-in user, belongs in a session, never in a plain cookie that the user can edit.

The examples are served with the built-in web server (php -S 127.0.0.1:8000) and requested with curl, which makes the headers and the cookie jar visible. A browser works the same way.

Main Features

PHP provides the following functions and superglobals for cookies and sessions.

Setting and reading a cookie

In the first example, we store the preferred theme in a cookie. On the first visit the cookie is set; on later visits it is read.

cookie.php
<?php

if (isset($_COOKIE['theme'])) {
    echo "Welcome back, theme is {$_COOKIE['theme']}\n";
} else {
    setcookie('theme', 'dark', [
        'expires'  => time() + 3600 * 24 * 30,
        'path'     => '/',
        'secure'   => false,     // true on HTTPS sites
        'httponly' => true,
        'samesite' => 'Lax',
    ]);
    echo "Cookie set\n";
}

The setcookie function accepts an options array since PHP 7.3. The expires option is a Unix timestamp; omit it for a cookie that lasts until the browser closes.

The httponly flag hides the cookie from JavaScript, and secure sends it only over HTTPS. The samesite option limits when the browser attaches the cookie to cross-site requests.

A cookie set during a request is not in $_COOKIE until the next request.

$ curl -si 127.0.0.1:8000/cookie.php
Set-Cookie: theme=dark; expires=Sat, 21 Nov 2026 19:57:25 GMT; Max-Age=2592000; path=/; HttpOnly; SameSite=Lax

Cookie set

$ curl -s -b "theme=dark" 127.0.0.1:8000/cookie.php
Welcome back, theme is dark

A simple session counter

A session lets us remember data on the server. The example counts how many times a user has visited the page.

counter.php
<?php

session_start();

if (!isset($_SESSION['visits'])) {
    $_SESSION['visits'] = 0;
}
$_SESSION['visits']++;

echo "Session ID: ", session_id(), "\n";
echo "Visits: {$_SESSION['visits']}\n";

Calling session_start creates a session on the first visit and sends the PHPSESSID cookie. Call it before sending any output.

On the following requests PHP reads the cookie and fills $_SESSION from the server-side storage, which is files in the temporary directory by default.

The curl options -b and -c read and write a cookie jar, as a browser would.

$ for i in 1 2 3; do curl -s -b jar.txt -c jar.txt 127.0.0.1:8000/counter.php; done
Session ID: 02c70316b26791ba79d084e9b80207ce
Visits: 1
Session ID: 02c70316b26791ba79d084e9b80207ce
Visits: 2
Session ID: 02c70316b26791ba79d084e9b80207ce
Visits: 3

Login and logout

The most common use of sessions is keeping a user logged in. The example implements login, logout and a protected page in one script.

login.php
<?php

declare(strict_types=1);

session_set_cookie_params([
    'lifetime' => 0,
    'path'     => '/',
    'secure'   => false,      // true behind HTTPS
    'httponly' => true,
    'samesite' => 'Strict',
]);
session_start();

$action = $_GET['action'] ?? 'status';

switch ($action) {
    case 'login':
        // credentials checked here, e.g. with password_verify()
        session_regenerate_id(true);
        $_SESSION['user'] = 'alice';
        $_SESSION['login_time'] = time();
        echo "Logged in as alice\n";
        break;

    case 'logout':
        $_SESSION = [];
        $p = session_get_cookie_params();
        setcookie(session_name(), '', [
            'expires'  => time() - 3600,
            'path'     => $p['path'],
            'httponly' => $p['httponly'],
            'samesite' => $p['samesite'],
        ]);
        session_destroy();
        echo "Logged out\n";
        break;

    default:
        if (isset($_SESSION['user'])) {
            echo "Hello, {$_SESSION['user']}\n";
        } else {
            http_response_code(401);
            echo "Not logged in\n";
        }
}

The cookie parameters must be set before session_start.

On login, we call session_regenerate_id(true) after the credentials have been verified (see the password hashing tutorial). If an attacker had planted a known session ID before the login, the old ID becomes useless. This defends against session fixation.

Logging out empties $_SESSION, expires the cookie in the browser, and destroys the server-side data. An unauthenticated request receives the status code 401.

$ curl -s -w "%{http_code}\n" -b jar.txt -c jar.txt 127.0.0.1:8000/login.php
Not logged in
401
$ curl -si -b jar.txt -c jar.txt "127.0.0.1:8000/login.php?action=login"
Set-Cookie: PHPSESSID=65827ad40a28896e07a32043ae7a9d09; path=/; HttpOnly; SameSite=Strict

Logged in as alice
$ curl -s -b jar.txt -c jar.txt 127.0.0.1:8000/login.php
Hello, alice
$ curl -s -b jar.txt -c jar.txt "127.0.0.1:8000/login.php?action=logout"
Logged out
$ curl -s -b jar.txt -c jar.txt 127.0.0.1:8000/login.php
Not logged in

CSRF token stored in the session

A cross-site request forgery (CSRF) makes the victim's browser send a request that carries their session cookie. The example protects a form with a token kept in the session.

csrf.php
<?php

declare(strict_types=1);

session_start();

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $_SESSION['csrf'] = bin2hex(random_bytes(32));
    echo "token: {$_SESSION['csrf']}\n";
    exit;
}

$sent = $_POST['csrf'] ?? '';

if (!hash_equals($_SESSION['csrf'] ?? '', $sent)) {
    http_response_code(403);
    echo "Invalid CSRF token\n";
    exit;
}

echo "Form accepted\n";

The form includes a random token which is also stored in the session. The server accepts the request only when the two match.

The comparison uses hash_equals, which takes the same time regardless of where the strings differ.

Together with SameSite cookies, this is the standard protection for forms that change state.

$ T=$(curl -s -b jar.txt -c jar.txt 127.0.0.1:8000/csrf.php | cut -d' ' -f2)
$ curl -s -b jar.txt -c jar.txt -d "csrf=$T" 127.0.0.1:8000/csrf.php
Form accepted
$ curl -s -b jar.txt -c jar.txt -d "csrf=bad" 127.0.0.1:8000/csrf.php
Invalid CSRF token

Session security checklist

The following rules help keep sessions secure.

Summary

In this article, we have used cookies and sessions to keep state across requests, implemented login and logout with session_regenerate_id, and added a CSRF token.

Author

My name is Jan Bodnar and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all PHP tutorials.