Middleware
php-via supports PSR-15 middleware for cross-cutting concerns like authentication,
CORS, logging, and rate limiting. Middleware runs on page and action requests by default;
implement SseAwareMiddleware to also run on SSE handshakes.
How it works
Middleware follows the standard PSR-15
onion model. Each middleware receives a PSR-7 ServerRequestInterface and can either
short-circuit (return a response directly) or delegate to the next handler. php-via converts the
OpenSwoole request to PSR-7 at the boundary and converts any PSR-7 response back, so the
middleware layer is zero-overhead when no middleware is registered.
Global middleware
Global middleware runs on every page and action request. Register it with
$app->middleware() before $app->start():
use Tuupola\Middleware\CorsMiddleware;
$app->middleware(new CorsMiddleware([
'origin' => ['https://example.com'],
'methods' => ['GET', 'POST'],
'headers.allow' => ['Content-Type', 'Authorization'],
'credentials' => true,
]));
Per-route middleware
Attach middleware to specific routes using the fluent API returned by $app->page():
$app->page('/admin', fn(Context $c) => $c->view('admin.html.twig'))
->middleware(new AuthMiddleware());
$app->page('/api/data', fn(Context $c) => $c->view('data.html.twig'))
->middleware(new AuthMiddleware(), new RateLimitMiddleware()); Per-route middleware runs after global middleware. The full pipeline is: global → route-specific → page handler.
Writing middleware
Implement Psr\Http\Server\MiddlewareInterface. Use
$request->withAttribute() to pass data downstream — the attributes are
automatically bridged to $context->getRequestAttribute() in your page handler.
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Nyholm\Psr7\Response;
class AuthMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$sessionId = $this->extractSessionId($request);
if (!$this->isAuthenticated($sessionId)) {
// Short-circuit: return 302 redirect to login
return new Response(302, ['Location' => '/login']);
}
// Pass user info downstream → available via $c->getRequestAttribute('user')
return $handler->handle(
$request->withAttribute('user', $this->getUser($sessionId))
);
}
}
$request->withAttribute() to pass data downstream.
SSE-aware middleware
By default, middleware only runs on page and action requests — not on the SSE handshake.
If you need middleware to also guard the SSE connection (e.g. auth), implement the
SseAwareMiddleware marker interface:
use Mbolli\PhpVia\Http\Middleware\SseAwareMiddleware;
class AuthMiddleware implements SseAwareMiddleware
{
// This now runs on page, action, AND SSE requests
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
// ...
}
}
Accessing middleware data in handlers
Middleware attributes set via $request->withAttribute() are automatically
bridged to the Context:
$app->page('/dashboard', function (Context $c): void {
$user = $c->getRequestAttribute('user');
$c->view('dashboard.html.twig', [
'name' => $user['name'],
]);
})->middleware(new AuthMiddleware());
Live example: Auth middleware
The Login Flow example demonstrates a working
AuthMiddleware that protects the /examples/login/dashboard route.
Unauthenticated users are redirected to the login form. Authenticated users see a personalised
dashboard with their session data.
Built-in security
php-via includes several security features out of the box, independent of middleware:
CSRF protection
Actions validate the Origin header against a configurable allowlist.
Configure with withTrustedOrigins():
$config = (new Config())
->withTrustedOrigins(['https://myapp.com', 'https://staging.myapp.com']); null(default) — no restriction; all origins pass (development-friendly)[]— block all browser-origin requests['https://example.com']— strict allowlist
Secure session cookies
Enable withSecureCookie() in production to set the __Host- cookie prefix,
which enforces HTTPS, Path=/, and no Domain attribute:
$config = (new Config())
->withSecureCookie(true); // __Host-via_session_id; Secure; SameSite=Lax
Action rate limiting
Built-in per-IP sliding-window rate limiter for action endpoints:
$config = (new Config())
->withActionRateLimit(100, 60); // max 100 actions per 60 seconds per IP
Proxy trust
If your app runs behind a reverse proxy (Caddy, nginx), enable proxy trust so X-Base-Path
headers are honoured:
$config = (new Config())
->withTrustProxy(true);
Next steps
- Actions — server-side event handlers
- Lifecycle — connect, disconnect, intervals, cleanup
- Deployment — production configuration