web php
Stateless Vs Stateful Authentication
In stateful authentication, the server stores the important login state and the browser sends a session ID. In stateless authentication, the client sends a token that carries enough signed information for the server to validate each request without looking up a server-side session record.
Neither approach is automatically better. They solve different problems and create different risks.
Stateful authentication
Traditional PHP web apps often use stateful authentication with sessions.
The browser sends a cookie such as PHPSESSID=abc123. The server uses that ID to load session data, then checks something like $_SESSION['user_id'].
<?php
declare(strict_types=1);
$session = ['user_id' => 42];
if (isset($session['user_id'])) {
echo 'Authenticated user: ' . $session['user_id'] . PHP_EOL;
}
// Prints:
// Authenticated user: 42
Stateful auth is easy to revoke: delete the server-side session or clear the user's sessions. It also works naturally with server-rendered pages.
The tradeoff is that the server needs session storage. In a multi-server environment, that usually means shared storage such as Redis or a database.
Stateless authentication
Stateless authentication commonly uses bearer tokens, such as API tokens or JWTs. The client sends a header like Authorization: Bearer ....
The server verifies the token signature, expiry, issuer, and claims. If the token is valid, the request is authenticated.
<?php
declare(strict_types=1);
$headers = [
'Authorization' => 'Bearer api_token_abc123',
];
$authorization = $headers['Authorization'] ?? '';
$hasBearerToken = str_starts_with($authorization, 'Bearer ');
echo $hasBearerToken ? 'Token supplied' : 'No token';
// Prints:
// Token supplied
Stateless auth is common for APIs, mobile clients, service-to-service calls, and systems where avoiding central session lookup is useful.
The tradeoff is revocation. If a signed token is valid for one hour, the server may accept it until expiry unless the application checks a deny-list, token version, or central revocation store. At that point the design becomes partly stateful again.
Browser apps and CSRF
Cookies are automatically sent by browsers. That makes stateful cookie-based auth convenient, but it also means state-changing requests need CSRF protection unless SameSite and request design fully cover the risk.
Bearer tokens in an Authorization header are not automatically attached by the browser to arbitrary forms, which changes the CSRF risk. But storing bearer tokens in browser storage can create a serious XSS risk because JavaScript may be able to read them.
Do not choose stateless tokens for a browser app just because they sound modern. For many server-rendered PHP apps, secure HTTP-only session cookies are a simpler and safer default.
What must be checked in stateless tokens
A token is not valid just because it can be decoded. A real stateless token check should verify at least:
- signature
- expiry
- issuer
- audience
- token type or purpose
- user status if the application needs immediate revocation
This example shows the shape of the checks without implementing JWT cryptography:
<?php
declare(strict_types=1);
$tokenClaims = [
'sub' => '42',
'exp' => 1_700_001_000,
'iss' => 'https://auth.example.test',
'aud' => 'orders-api',
];
$now = 1_700_000_000;
$isUsable = $tokenClaims['exp'] > $now
&& $tokenClaims['iss'] === 'https://auth.example.test'
&& $tokenClaims['aud'] === 'orders-api';
echo $isUsable ? 'Token claims accepted' : 'Token claims rejected';
// Prints:
// Token claims accepted
In production, use a maintained library for token validation rather than hand-parsing and hand-verifying cryptographic tokens.
Choosing between them
Use stateful sessions when you are building a normal server-rendered PHP application, need simple logout/revocation, and can provide reliable session storage.
Use stateless bearer tokens when you are building APIs for non-browser clients, service-to-service communication, or distributed systems where each request needs to carry its own authentication proof.
Many real systems use both. A website may use stateful sessions for browser users and stateless tokens for API clients.
What you should be able to do
After this lesson, you should be able to explain where login state lives in each approach, describe the revocation and storage tradeoffs, understand how browser cookies affect CSRF, and choose a sensible default for a PHP web app or API.
Practice
Task: Classify Authentication Flows
Write a small PHP script that classifies request examples as stateful, stateless, or unauthenticated.
Requirements
- Use
declare(strict_types=1);. - Create a function that accepts request cookies and headers.
- Treat a session cookie as stateful authentication.
- Treat an
Authorization: Bearer ...header as stateless authentication. - Treat requests with neither value as unauthenticated.
- Include all three cases in the output.
Check Your Work
Run the script and confirm that the result is based on where the authentication proof lives.
Show solution
<?php
declare(strict_types=1);
/**
* @param array<string, string> $cookies
* @param array<string, string> $headers
*/
function classifyAuthentication(array $cookies, array $headers): string
{
if (isset($cookies['PHPSESSID']) && $cookies['PHPSESSID'] !== '') {
return 'stateful session';
}
$authorization = $headers['Authorization'] ?? '';
if (str_starts_with($authorization, 'Bearer ')) {
return 'stateless bearer token';
}
return 'unauthenticated';
}
$requests = [
'browser page' => [
'cookies' => ['PHPSESSID' => 'sess_abc123'],
'headers' => [],
],
'api call' => [
'cookies' => [],
'headers' => ['Authorization' => 'Bearer api_token_abc123'],
],
'public page' => [
'cookies' => [],
'headers' => [],
],
];
foreach ($requests as $name => $request) {
echo $name . ': '
. classifyAuthentication($request['cookies'], $request['headers'])
. PHP_EOL;
}
// Prints:
// browser page: stateful session
// api call: stateless bearer token
// public page: unauthenticated
In a real application, classification is only the first step. Stateful auth still needs session lookup, and stateless auth still needs token validation, expiry checks, and revocation design.
Why This Works
The examples cover the three common cases. The session cookie case depends on server-side state. The bearer header case sends the proof on the request. The public request has no authentication proof.