Middleware
Introduction
Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, Slenix includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to your application's login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.
Additional middleware can be written to perform a variety of tasks besides authentication. For example, a CORS middleware might be responsible for adding the proper headers to all responses leaving your application. A rate limiting middleware might restrict the number of requests a given IP address can make within a time window.
Slenix includes several built-in middleware aliases for common tasks such as authentication, CORS, JWT validation, and rate limiting. All of your application's custom middleware is typically stored in the app/Middlewares/ directory.
Defining Middleware
To create a new middleware, use the make:middleware Celestial command:
php celestial make:middleware EnsureTokenIsValidThis command will place a new EnsureTokenIsValidMiddleware class within your app/Middlewares/ directory. In this middleware, we will only allow access to the route if a supplied token matches a known value. Otherwise, we will redirect the user back to the home page:
namespace App\Middlewares;
use Slenix\Http\Request;
use Slenix\Http\Response;
use Slenix\Http\Middlewares\Middleware;
class EnsureTokenIsValidMiddleware implements Middleware
{
public function handle(Request $request, Response $response, callable $next): mixed
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('/');
}
return $next($request, $response);
}
}As you can see, if the given token does not match our secret token, the middleware will return an HTTP redirect to the client. Otherwise, the request will be passed further into the application by calling $next($request, $response).
It is best to envision middleware as a series of "layers" that HTTP requests must pass through before they hit your application. Each layer can examine the request and even reject it entirely.
Tip: All middleware classes must implement the
Slenix\Http\Middlewares\Middlewareinterface and define thehandlemethod.
The Middleware Interface
Every middleware in Slenix must implement the following contract:
namespace Slenix\Http\Middlewares;
use Slenix\Http\Request;
use Slenix\Http\Response;
interface Middleware
{
public function handle(Request $request, Response $response, callable $next): mixed;
}The handle method receives three arguments:
| Argument | Type | Description |
|---|---|---|
$request | Request | The incoming HTTP request |
$response | Response | The outgoing HTTP response |
$next | callable | The next middleware or route handler in the pipeline |
Before and After Middleware
Whether a middleware runs before or after a request is handled depends on the middleware itself. For example, the following middleware would perform some task before the request is handled by the application:
public function handle(Request $request, Response $response, callable $next): mixed
{
// Perform action before the request is handled
// e.g. validate a token, check a session, log the request
return $next($request, $response);
}However, this middleware would perform its task after the request is handled by the application:
public function handle(Request $request, Response $response, callable $next): mixed
{
$result = $next($request, $response);
// Perform action after the request is handled
// e.g. add a response header, log the duration
return $result;
}Registering Middleware
Route Middleware
If you would like to assign middleware to specific routes, you may chain the middleware method when defining the route:
use Slenix\Http\Routing\Router;
Router::get('/profile', [ProfileController::class, 'index'])
->middleware('auth');You may assign multiple middleware to a route by passing an array:
Router::get('/settings', [SettingsController::class, 'index'])
->middleware(['auth', 'throttle']);You may also use the full class name instead of an alias:
Router::get('/profile', [ProfileController::class, 'index'])
->middleware(\App\Middlewares\AuthMiddleware::class);Global Middleware
If you want a middleware to run during every HTTP request to your application, you may register it as a global middleware. Global middleware is prepended to every route automatically, regardless of group or individual route definitions:
Router::globalMiddleware([
\App\Middlewares\CorsMiddleware::class,
\App\Middlewares\ThrottleMiddleware::class,
]);Assigning Middleware to Route Groups
You may also assign middleware to a group of routes, so that every route inside the group shares the same middleware stack:
Router::group(['middleware' => ['auth', 'verified']], function () {
Router::get('/dashboard', [DashboardController::class, 'index']);
Router::get('/settings', [SettingsController::class, 'index']);
Router::post('/settings', [SettingsController::class, 'update']);
});When using Router::middleware, you may assign middleware to a group without a URI prefix:
Router::middleware('auth', function () {
Router::get('/profile', [ProfileController::class, 'index']);
Router::get('/billing', [BillingController::class, 'index']);
});Middleware Aliases
Slenix resolves short string aliases to their full middleware class names automatically. The following aliases are available out of the box:
| Alias | Class |
|---|---|
auth | App\Middlewares\AuthMiddleware |
guest | App\Middlewares\GuestMiddleware |
cors | App\Middlewares\CorsMiddleware |
jwt | App\Middlewares\JwtMiddleware |
throttle | App\Middlewares\ThrottleMiddleware |
You may always pass the fully qualified class name if you prefer to be explicit:
Router::get('/cl-admin', [AdminController::class, 'index'])
->middleware(\App\Middlewares\AuthMiddleware::class);Middleware Pipeline
Slenix executes middleware in reverse declaration order, building a nested pipeline identical to how Laravel handles the middleware stack. The last declared middleware wraps the outermost layer of the execution pipeline, and the first declared middleware is the closest to the route handler.
Given this configuration:
Router::get('/dashboard', [DashboardController::class, 'index'])
->middleware(['cors', 'throttle', 'auth']);The execution order is:
Request
→ CorsMiddleware::handle()
→ ThrottleMiddleware::handle()
→ AuthMiddleware::handle()
→ DashboardController::index()
← AuthMiddleware (after $next)
← ThrottleMiddleware (after $next)
← CorsMiddleware (after $next)
ResponseTo halt the pipeline — for example, when a user is not authenticated — simply do not call $next and return or redirect instead:
public function handle(Request $request, Response $response, callable $next): mixed
{
if (!session()->has('user_id')) {
// Pipeline stops here — $next is never called
redirect('/login')->with('error', 'Please sign in to continue.');
}
return $next($request, $response); // Pipeline continues
}Practical Examples
Authentication Middleware
The most common middleware pattern in any web application. Protect all routes that require a logged-in user:
namespace App\Middlewares;
use Slenix\Http\Request;
use Slenix\Http\Response;
use Slenix\Http\Middlewares\Middleware;
class AuthMiddleware implements Middleware
{
public function handle(Request $request, Response $response, callable $next): mixed
{
if (!session()->has('user_id')) {
redirect('/login')->with('error', 'Please sign in to continue.');
}
return $next($request, $response);
}
}Guest Middleware
Redirect already authenticated users away from pages they should not access, such as the login or registration pages:
namespace App\Middlewares;
use Slenix\Http\Request;
use Slenix\Http\Response;
use Slenix\Http\Middlewares\Middleware;
class GuestMiddleware implements Middleware
{
public function handle(Request $request, Response $response, callable $next): mixed
{
if (session()->has('user_id')) {
redirect('/dashboard');
}
return $next($request, $response);
}
}CORS Middleware
Add the appropriate cross-origin headers to every response leaving your application:
namespace App\Middlewares;
use Slenix\Http\Request;
use Slenix\Http\Response;
use Slenix\Http\Middlewares\Middleware;
class CorsMiddleware implements Middleware
{
public function handle(Request $request, Response $response, callable $next): mixed
{
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-CSRF-TOKEN');
// Respond immediately to preflight OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
return $next($request, $response);
}
}JWT Middleware
Validate a Bearer token on every API request:
namespace App\Middlewares;
use Slenix\Http\Request;
use Slenix\Http\Response;
use Slenix\Http\Middlewares\Middleware;
use Slenix\Supports\Security\JWT;
class JwtMiddleware implements Middleware
{
public function handle(Request $request, Response $response, callable $next): mixed
{
$token = $this->extractToken();
if (!$token || !JWT::validate($token)) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'Unauthorized — invalid or missing token.']);
exit;
}
return $next($request, $response);
}
private function extractToken(): ?string
{
$header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (str_starts_with($header, 'Bearer ')) {
return substr($header, 7);
}
return null;
}
}Throttle Middleware
Limit the number of requests from a single IP address within a time window:
namespace App\Middlewares;
use Slenix\Http\Request;
use Slenix\Http\Response;
use Slenix\Http\Middlewares\Middleware;
class ThrottleMiddleware implements Middleware
{
private int $maxRequests;
private int $windowSeconds;
public function __construct(int $maxRequests = 100, int $windowSeconds = 60)
{
$this->maxRequests = $maxRequests;
$this->windowSeconds = $windowSeconds;
}
public function handle(Request $request, Response $response, callable $next): mixed
{
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$key = 'throttle_' . md5($ip);
$count = (int) session()->get($key, 0);
$resetAt = (int) session()->get($key . '_reset', 0);
$now = time();
if ($now > $resetAt) {
$count = 0;
$resetAt = $now + $this->windowSeconds;
session()->put($key . '_reset', $resetAt);
}
if ($count >= $this->maxRequests) {
http_response_code(429);
header('Retry-After: ' . ($resetAt - $now));
header('Content-Type: application/json');
echo json_encode(['error' => 'Too many requests. Please slow down.']);
exit;
}
session()->put($key, $count + 1);
return $next($request, $response);
}
}Middleware and CSRF Protection
Slenix automatically validates CSRF tokens for all state-mutating requests (POST, PUT, PATCH, DELETE) at the router level — no middleware configuration is required for basic CSRF protection.
If you need to exclude specific routes from CSRF validation (such as webhook endpoints), configure the $csrfExcept array in the Kernel rather than disabling CSRF in a middleware:
// src/Core/Kernel.php
private array $csrfExcept = [
'/api/*',
'/webhook/stripe',
];For more information, refer to the CSRF Protection documentation.
Method Reference
Middleware Interface
interface Middleware
{
public function handle(Request $request, Response $response, callable $next): mixed;
}Registration Methods
| Method | Where | Description |
|---|---|---|
->middleware('alias') | Route | Attach middleware to a single route |
->middleware(['a', 'b']) | Route | Attach multiple middleware to a single route |
->middlewares(...) | Route | Alias for middleware() |
Router::middleware('alias', fn) | Group | Create a middleware-only group |
Router::group(['middleware' => [...]], fn) | Group | Attach middleware to a route group |
Router::globalMiddleware([...]) | Global | Apply middleware to all routes |