Skip to content

Php Middleware

DodaTech 4 min read

title: PHP Middleware — Complete Guide to PSR-15 Middleware description: 'Learn PHP middleware with PSR-15: request handlers, response interfaces, middleware chains, Slim framework, authentication middleware, logging, and CORS.' date: 2026-06-28 lastmod: 2026-06-28 weight: 42 tags: [backend, php]


PSR-15 middleware defines a standard interface for HTTP middleware in PHP, enabling reusable request processing pipelines for authentication, logging, CORS, and input validation.

## What You'll Learn

By the end of this tutorial, you'll understand PSR-15 interfaces, build custom middleware, chain middleware in Slim or Laravel, create auth and CORS middleware, and test middleware components.

## Real-World Use

A Slim API uses a middleware stack: CORS middleware runs first, then logging, then JWT authentication, then rate limiting, then the route handler. Each middleware filters or transforms the request.

## Middleware Learning Path

```mermaid
flowchart LR
  A[MVC] --> B[REST API]
  B --> C[JWT Auth]
  C --> D[Middleware]
  D --> E[Testing]
  C --> F{You Are Here}
  style F fill:#f90,color:#fff

PSR-15 Interfaces

<?php
// PSR-15 interfaces
namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
interface MiddlewareInterface {
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
}
interface RequestHandlerInterface {
    public function handle(ServerRequestInterface $request): ResponseInterface;
}

CORS Middleware

<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class CorsMiddleware implements MiddlewareInterface {
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        if ($request->getMethod() === "OPTIONS") {
            $response = new \Slim\Psr7\Response(204);
        } else {
            $response = $handler->handle($request);
        }
        return $response
            ->withHeader("Access-Control-Allow-Origin", "*")
            ->withHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
            ->withHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
            ->withHeader("Access-Control-Max-Age", "86400");
    }
}

JWT Auth Middleware

<?php
namespace App\Middleware;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class JwtAuthMiddleware implements MiddlewareInterface {
    public function __construct(private string $secret) {}
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        $authHeader = $request->getHeaderLine("Authorization");
        if (!preg_match("/^Bearer\s+(.+)$/", $authHeader, $matches)) {
            $response = new \Slim\Psr7\Response(401);
            $response->getBody()->write(json_encode(["error" => "No token"]));
            return $response->withHeader("Content-Type", "application/json");
        }
        try {
            $decoded = JWT::decode($matches[1], new Key($this->secret, "HS256"));
            $request = $request->withAttribute("user", $decoded);
            return $handler->handle($request);
        } catch (\Exception $e) {
            $response = new \Slim\Psr7\Response(401);
            $response->getBody()->write(json_encode(["error" => "Invalid token"]));
            return $response->withHeader("Content-Type", "application/json");
        }
    }
}

Logging Middleware

<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
class LoggingMiddleware implements MiddlewareInterface {
    public function __construct(private LoggerInterface $logger) {}
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        $start = microtime(true);
        $response = $handler->handle($request);
        $duration = (microtime(true) - $start) * 1000;
        $this->logger->info(sprintf(
            "%s %s - %d (%.0fms)",
            $request->getMethod(),
            $request->getUri()->getPath(),
            $response->getStatusCode(),
            $duration
        ));
        return $response;
    }
}

Using Middleware in Slim

<?php
use Slim\Factory\AppFactory;
$app = AppFactory::create();
$app->add(new \App\Middleware\CorsMiddleware());
$app->add(new \App\Middleware\JwtAuthMiddleware("your-secret"));
$app->add(new \App\Middleware\LoggingMiddleware($logger));
$app->get("/api/users", function ($request, $response) {
    $user = $request->getAttribute("user");
    $response->getBody()->write(json_encode(["user_id" => $user->sub]));
    return $response->withHeader("Content-Type", "application/json");
});
$app->run();

Rate Limiting Middleware

<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RateLimitMiddleware implements MiddlewareInterface {
    public function __construct(private \Redis $redis, private int $maxRequests = 100, private int $window = 60) {}
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        $ip = $request->getServerParams()["REMOTE_ADDR"] ?? "unknown";
        $key = "rate_limit:$ip";
        $current = (int)$this->redis->get($key);
        if ($current >= $this->maxRequests) {
            $response = new \Slim\Psr7\Response(429);
            $response->getBody()->write(json_encode(["error" => "Too many requests"]));
            return $response->withHeader("Content-Type", "application/json")
                ->withHeader("Retry-After", (string)$this->window);
        }
        $this->redis->incr($key);
        if ($current === 0) $this->redis->expire($key, $this->window);
        return $handler->handle($request);
    }
}

Common Mistakes

1. Processing Response After Handler

Middleware can modify requests before and responses after. The response from $handler->handle() can be modified before returning.

2. Not Returning Response

Every middleware must return a ResponseInterface. Forgetting to return causes runtime errors.

3. Modifying Request Attributes Incorrectly

PSR-7 messages are immutable. Use withAttribute() which returns a new request object.

4. Ordering Middleware Incorrectly

The first middleware added wraps the outermost layer. CORS should be outer, auth should be inner.

5. Blocking OPTIONS Requests

CORS preflight OPTIONS requests must be handled before auth middleware checks the token.

Practice Questions

1. What is PSR-15?

PHP Standard Recommendation 15 defines MiddlewareInterface and RequestHandlerInterface for HTTP middleware.

2. How does middleware chain work?

Each middleware calls $handler->handle($request) to pass to the next. The innermost handler returns the final response.

3. What is the difference between request and response middleware?

Request middleware processes the incoming request. Response middleware processes the outgoing response after the handler.

4. How do you add middleware in Slim 4?

Use $app->add(new MiddlewareClass()). Middleware runs in the order added (first added = outermost).

5. Challenge: Create a middleware that adds security headers to all responses.

<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class SecurityHeadersMiddleware implements MiddlewareInterface {
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        $response = $handler->handle($request);
        return $response
            ->withHeader("X-Content-Type-Options", "nosniff")
            ->withHeader("X-Frame-Options", "DENY")
            ->withHeader("X-XSS-Protection", "1; mode=block")
            ->withHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
            ->withHeader("Content-Security-Policy", "default-src 'self'");
    }
}

FAQ

Can I use middleware without a framework?

Yes. Create classes implementing MiddlewareInterface and build your own pipeline with a Dispatcher.

What is the difference between PSR-7 and PSR-15?

PSR-7 defines HTTP message interfaces (Request, Response). PSR-15 defines middleware and request handler interfaces.

How do I pass data between middleware?

Use request attributes: $request->withAttribute('user', $decoded). Read in next middleware with $request->getAttribute('user').

What frameworks support PSR-15?

Slim 4, Mezzio (formerly Zend Expressive), Laravel (uses its own middleware system but similar concept).

How do I test middleware?

Create a mock request, instantiate the middleware with a mock handler that returns a dummy response, and assert response headers/status.

Mini Project: Slim API with Middleware Stack

Build a Slim 4 application with CORS, JWT auth, logging, and rate limiting middleware.

<?php
use Slim\Factory\AppFactory;
use App\Middleware\CorsMiddleware;
use App\Middleware\JwtAuthMiddleware;
use App\Middleware\LoggingMiddleware;
require "vendor/autoload.php";
$app = AppFactory::create();
$app->add(new CorsMiddleware());
$app->add(new JwtAuthMiddleware("my-secret-key"));
$app->add(new LoggingMiddleware($logger));
$app->get("/api/me", function ($request, $response) {
    $user = $request->getAttribute("user");
    $response->getBody()->write(json_encode(["id" => $user->sub, "role" => $user->role]));
    return $response->withHeader("Content-Type", "application/json");
});
$app->run();

What's Next

PHP Performance PHP Docker Deployment PHP Project

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro