web php

Middleware Concepts

Middleware is code that runs around a request handler. It can inspect the request before the controller runs, stop the request early, or modify the response on the way back out.

Frameworks use middleware for authentication, CSRF protection, rate limiting, request logging, trusted proxies, CORS, and security headers.

A request passes through layers

Middleware is often described as a pipeline:

request -> middleware -> middleware -> controller -> middleware -> middleware -> response

This small example uses functions to show the idea:

PHP example
<?php

declare(strict_types=1);

/**
 * @param array{user_id?: int} $request
 */
function requireLogin(array $request, callable $next): string
{
    if (!isset($request['user_id'])) {
        return '401 Authentication required';
    }

    return $next($request);
}

$controller = static fn (array $request): string => 'Dashboard for user ' . $request['user_id'];

echo requireLogin(['user_id' => 42], $controller) . PHP_EOL;
echo requireLogin([], $controller) . PHP_EOL;

// Prints:
// Dashboard for user 42
// 401 Authentication required

The middleware can choose to call $next() or return early.

Middleware can modify responses

Some middleware lets the controller run, then adds headers to the response.

PHP example
<?php

declare(strict_types=1);

/**
 * @param array{status: int, headers: array<string, string>, body: string} $response
 * @return array{status: int, headers: array<string, string>, body: string}
 */
function addSecurityHeader(array $response): array
{
    $response['headers']['X-Content-Type-Options'] = 'nosniff';

    return $response;
}

$response = ['status' => 200, 'headers' => [], 'body' => 'OK'];
$response = addSecurityHeader($response);

echo $response['headers']['X-Content-Type-Options'] . PHP_EOL;

// Prints:
// nosniff

Order matters

Middleware order can change behaviour. For example:

  1. trusted proxy middleware adjusts scheme and client IP
  2. session middleware starts the session
  3. authentication middleware loads the user
  4. authorization middleware checks access

If authentication runs before the session exists, it may not find the logged-in user. If rate limiting uses the wrong client IP because trusted proxies were not handled first, it may limit the proxy instead of the real client.

Global and route middleware

Global middleware runs for most or all requests, such as logging, trusted proxies, and security headers.

Route middleware runs only for selected routes, such as auth for dashboards or admin for admin routes.

PHP example
<?php

declare(strict_types=1);

$routes = [
    '/dashboard' => ['middleware' => ['auth'], 'handler' => 'DashboardController'],
    '/admin/users' => ['middleware' => ['auth', 'admin'], 'handler' => 'AdminUsersController'],
];

echo implode(', ', $routes['/admin/users']['middleware']) . PHP_EOL;

// Prints:
// auth, admin

What belongs in middleware

Middleware is good for cross-cutting behaviour that applies to many routes. Authentication checks, request IDs, rate limits, and security headers are good examples.

Middleware is a poor place for feature-specific business rules. If only one controller action needs the rule, keeping it near that action may be clearer.

What to check in a project

Check middleware order when debugging authentication, session, proxy, CORS, and rate-limit issues.

Check whether a route has the middleware it needs. A missing auth middleware can expose a page.

Check whether middleware returns early. A request may never reach the controller.

Check response-modifying middleware when headers appear or disappear.

What you should be able to do

After this lesson, you should be able to explain middleware as code around a request handler, identify short-circuiting middleware, recognise response-modifying middleware, and understand why middleware order matters.

Practice

Task: Build A Middleware Pipeline

Write a small PHP script that models an authentication middleware around a controller.

Requirements

  • Use declare(strict_types=1);.
  • Represent the request as an array.
  • Create middleware that requires user_id.
  • If user_id is missing, return a 401 message.
  • If user_id exists, call the next handler.
  • Include one authenticated case and one unauthenticated case.
  • Print both outputs.

Check Your Work

Run the script and confirm the unauthenticated request never reaches the controller.

Show solution

This solution uses a callable $next to represent the next layer in the request pipeline.

PHP example
<?php

declare(strict_types=1);

/**
 * @param array{user_id?: int} $request
 */
function requireLogin(array $request, callable $next): string
{
    if (!isset($request['user_id'])) {
        return '401 Authentication required';
    }

    return $next($request);
}

$controller = static function (array $request): string {
    return 'Dashboard for user ' . $request['user_id'];
};

echo requireLogin(['user_id' => 42], $controller) . PHP_EOL;
echo requireLogin([], $controller) . PHP_EOL;

// Prints:
// Dashboard for user 42
// 401 Authentication required

Why This Works

The authenticated request reaches the controller. The unauthenticated request is stopped by middleware, which is exactly what auth middleware is supposed to do.