Php Middleware
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
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