Core Concepts

Middleware

Using and creating middleware in Kipchak.

Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application.

What is Middleware?

Kipchak is built on the Slim Framework, which uses PSR-15 middleware. Middleware acts as a layer between the client request and your route handler, allowing you to:

  • Authenticate and authorize requests
  • Log request/response data
  • Add security headers
  • Rate limit requests
  • Parse and validate input
  • Transform responses

Middleware executes in a chain, with each piece of middleware deciding whether to pass the request to the next middleware or return early.

How Middleware Works

Middleware executes in LIFO (Last In, First Out) order:

$app->add(new ThirdMiddleware());   // Executes third
$app->add(new SecondMiddleware());  // Executes second
$app->add(new FirstMiddleware());   // Executes first

Request flow: FirstMiddleware → SecondMiddleware → ThirdMiddleware → RouteHandler Response flow: RouteHandler → ThirdMiddleware → SecondMiddleware → FirstMiddleware

Applying Middleware

Global Middleware

Apply middleware to all routes by registering it in middlewares/middlewares.php:

Middlewares written for Kipchak must adhere to the middleware interface and implement the initialise method.

<?php

use Kipchak\Middleware\Subashi\Subashi;
use Kipchak\Middleware\Error\Error;
use Kipchak\Middleware\Auth\Key\Key;
use Kipchak\Middleware\Auth\JWKS\JWKS;
use Slim\App;

/**
 * @var App $app
 */
Error::initialise($app);    // Error handling for routes
Key::initialise($app);      // API key authentication
JWKS::initialise($app);     // JWKS authentication
Subashi::initialise($app);  // WAF
Error::initialise($app);    // Error handling for every middleware above

Middleware added here applies to every request. Slim runs the middleware added last first, so initialise Error both first and last, with every other middleware between the two: see Error Middleware.

Route Group Middleware

Apply middleware to specific route groups:

<?php

use Api\Controllers;
use Kipchak\Middleware\Auth\JWKS\Handlers\AuthJWKS;
use Slim\Routing\RouteCollectorProxy;

$app->group('/v1', function(RouteCollectorProxy $group) {

    $group->get('/protected',
        [
            Controllers\v1\Protected::class,
            'get'
        ]
    );

})->add(new AuthJWKS($app->getContainer(), ['email']));

This middleware only applies to routes within the /v1 group.

Single Route Middleware

Apply middleware to individual routes:

$app->get('/admin', [Controllers\Admin::class, 'dashboard'])
   ->add(new AdminAuthMiddleware());

Built-in Middleware

Error Middleware

Handles exceptions and errors, providing structured error responses:

use Kipchak\Middleware\Error\Error;

Error::initialise($app);
// ... every other global middleware
Error::initialise($app);

Initialise it twice, as the first and the last line of middlewares/middlewares.php. Slim runs the middleware added last first, so:

  • The first is the innermost middleware. It turns an exception thrown by a route, such as an HttpNotFoundException, into a JSON error response, and every other middleware sees that response. Middleware that observes responses, such as OpenTelemetry, Metering, Redaction and Idempotency, relies on this.
  • The last is the outermost middleware. It turns an exception thrown by any other middleware, such as an HttpUnauthorizedException from your own authentication middleware, into the same JSON error response. Without it, that exception reaches the worker in html/index.php, and the client receives a plain-text 500.

The starter project's tests/MiddlewareOrderTest.php fails if the Error middleware is not the outermost.

Configured via kipchak.api.php:

return [
    'logExceptions' => true,
    'logExceptionDetails' => true,
];

API Key Authentication

Validates API keys via headers or query parameters:

use Kipchak\Middleware\Auth\Key\Key;
use Kipchak\Middleware\Auth\Key\Handlers\ApiKey;

// Global
Key::initialise($app);

// Specific routes
$app->group('/v1', function($group) {
    // Routes here
})->add(new ApiKey($app->getContainer()));

Configuration in kipchak.auth.key.php:

return [
    'enabled' => false,
    'ignore_options' => true,
    'ignore_paths' => ['/status'],
    'authorised_keys' => [
        'key1' => 'client1',
        'key2' => 'client2',
    ],
    'header' => 'apikey',
    'query_param' => 'apikey'
];

API keys can be provided as:

  • Header: X-API-Key: key1 or custom header
  • Query parameter: ?apikey=key1

JWKS Authentication

Validates JSON Web Tokens using JSON Web Key Sets:

use Kipchak\Middleware\Auth\JWKS\JWKS;
use Kipchak\Middleware\Auth\JWKS\Handlers\AuthJWKS;

// Global
JWKS::initialise($app);

// Specific routes with custom scopes
$app->group('/v1', function($group) {
    // Routes here
})->add(new AuthJWKS($app->getContainer(), ['email', 'profile']));

Configuration in kipchak.auth.jwks.php:

return [
    'enabled' => false,
    'ignore_options' => true,
    'ignore_paths' => ['/status'],
    'jwksUri' => 'https://auth.example.com/certs',
    'validate_scopes' => true,
    'scopes' => ['email', 'profile'],
];

The middleware:

  • Validates JWT signature using JWKS
  • Checks token expiration
  • Validates required scopes
  • Stores decoded token in container: $this->container->get('token')

Subashi WAF

Web Application Firewall with IP filtering, rate limiting, and request blocking:

use Kipchak\Middleware\Subashi\Subashi;

Subashi::initialise($app);

Configuration in kipchak.subashi.php supports:

Whitelisting:

'whitelist' => [
    [
        'name' => 'Whitelist trusted IPs',
        'conditions' => [
            [
                'type' => 'ip',
                'operator' => 'in_list',
                'values' => ['127.0.0.1', '::1'],
            ],
        ],
    ],
],

Blacklisting:

'blacklist' => [
    [
        'name' => 'Block bots',
        'conditions' => [
            [
                'type' => 'header',
                'key' => 'User-Agent',
                'operator' => 'regex',
                'value' => '/(bot|crawler|scraper)/i',
            ],
        ],
    ],
],

Rate Limiting:

'rate_limiting' => [
    'enabled' => true,
    'default_limit' => 100,
    'default_window' => 60,
    'store' => 'memcached',
],

'rate_limit_rules' => [
    [
        'name' => 'Per IP rate limit',
        'conditions' => [],
        'rate_limit' => [
            'limit' => 100,
            'window' => 60,
            'key_prefix' => 'ip',
            'key_source' => 'ip',
        ],
    ],
    [
        'name' => 'Per API key rate limit',
        'conditions' => [
            [
                'type' => 'header',
                'key' => 'apikey',
                'operator' => 'exists',
            ],
        ],
        'rate_limit' => [
            'limit' => 1000,
            'window' => 60,
            'key_prefix' => 'apikey',
            'key_source' => 'header:apikey',
        ],
    ],
],

Creating Custom Middleware

Create custom middleware by implementing PSR-15's MiddlewareInterface:

<?php

namespace Api\Middlewares;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class CustomMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {

        // Before route handler
        // Modify request, check conditions, etc.

        $request = $request->withAttribute('custom_data', 'value');

        // Call next middleware/handler
        $response = $handler->handle($request);

        // After route handler
        // Modify response, add headers, etc.

        $response = $response->withHeader('X-Custom-Header', 'value');

        return $response;
    }
}

Middleware with Dependencies

Use the container for dependency injection:

<?php

namespace Api\Middlewares;

use Kipchak\Driver\Config\Config;
use Kipchak\Driver\Logger\Logger;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AuthMiddleware implements MiddlewareInterface
{
    private ContainerInterface $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {

        $logger = Logger::get();
        $config = Config::get('kipchak.api'); // an array

        // Your middleware logic

        return $handler->handle($request);
    }
}

Apply it:

$app->group('/v1', function($group) {
    // Routes
})->add(new AuthMiddleware($app->getContainer()));

Middleware Execution Order

Slim runs the middleware added last first. Global middleware runs before route and group middleware, and among the middleware added to a route, the one added last runs first:

// In middlewares/middlewares.php
Error::initialise($app);      // innermost global middleware
Key::initialise($app);
JWKS::initialise($app);
Subashi::initialise($app);
Error::initialise($app);      // outermost: runs first

// On a route
$app->get('/test', $handler)
   ->add(new CacheMiddleware())
   ->add(new LogMiddleware());

Request: Error (last) → Subashi → JWKS → Key → Error (first) → Log → Cache → Handler Response: Handler → Cache → Log → Error (first) → Key → JWKS → Subashi → Error (last)

Best Practices

  1. Order matters - Add middleware in logical order (authentication before authorization)
  2. Keep middleware focused - Each middleware should do one thing well
  3. Use global middleware sparingly - Only for truly global concerns (error handling, logging)
  4. Leverage route groups - Apply middleware to groups of related routes
  5. Handle errors gracefully - Always consider failure cases
  6. Use the container - Access shared services via dependency injection
  7. Document behavior - Clearly document what each middleware does
  8. Test thoroughly - Middleware affects all requests, test edge cases

Available Middleware Packages

Browse available middleware at https://1x.ax/mamluk/kipchak/middlewares

The official middleware packages are:

  • Error - Exception and error handling
  • Subashi - WAF with rate limiting and IP filtering
  • Auth (JWKS) - JWT authentication via JWKS
  • Auth (Key) - API key authentication
  • Auth (Request Signing) - HMAC-signed requests and webhooks with replay protection
  • Subashi Pro (Enterprise) - Enterprise WAF with geo and ASN rules
  • Auth (JWT) (Enterprise) - Advanced JWT authentication
  • Usage Metering (Enterprise) - Per-consumer usage events for billing
  • Quotas (Enterprise) - Per-plan limits on requests and credits
Previous
Drivers