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