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.
- setcookie: sends a
Set-Cookieheader; it must be called before any output. - $_COOKIE: the cookies sent by the browser with the current request.
- session_start: starts or resumes a session and loads
$_SESSION. - $_SESSION: an array that persists between requests of one user.
- session_regenerate_id: issues a new session ID to prevent session fixation.
- session_destroy: deletes the session data on the server.
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.
<?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.
<?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.
<?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.
<?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.
- Serve the site over HTTPS and set the
secure,httponlyandsamesitecookie flags. - Call
session_regenerate_id(true)whenever the privilege level changes, especially at login. - Keep
session.use_strict_mode=1(the default in currentphp.inifiles) so PHP rejects session IDs it did not create, andsession.use_only_cookies=1so IDs never appear in URLs. - Store only what is needed in
$_SESSION, such as the user ID; load the rest from the database. - Set an idle timeout on the server by saving a last-activity timestamp, and destroy stale sessions.
- Release the session lock early with
session_write_close()in long-running scripts, because file-based sessions block parallel requests of the same user.
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
List all PHP tutorials.