Core Concepts

Middleware

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
Key::initialise($app);      // API key authentication
JWKS::initialise($app);     // JWKS authentication
Subashi::initialise($app);  // WAF

Middleware added here applies to every request.

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);

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'],
            ],
        ],
        'action' => 'allow',
    ],
],

Blacklisting:

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

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 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 = $this->container->get('logger');
        $config = $this->container->get('config');

        // Your middleware logic

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

Apply it:

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

Middleware Execution Order

Understanding execution order is crucial:

// In middlewares/middlewares.php
Error::initialise($app);      // 4th in, 1st out
Key::initialise($app);        // 3rd in, 2nd out
JWKS::initialise($app);       // 2nd in, 3rd out
Subashi::initialise($app);    // 1st in, 4th out

// On a route
$app->get('/test', $handler)
   ->add(new CacheMiddleware())    // 5th in, 5th out (runs after globals)
   ->add(new LogMiddleware());     // 6th in, 6th out

Request: Log → Cache → Subashi → JWKS → Key → Error → Handler Response: Handler → Error → Key → JWKS → Subashi → Cache → Log

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

Popular middleware includes:

  • Error - Exception and error handling
  • Subashi - WAF with rate limiting and IP filtering
  • Auth (JWKS) - JWT authentication via JWKS
  • Auth (Key) - API key authentication
  • CORS - Cross-Origin Resource Sharing
  • JSON - JSON request/response handling
Previous
Drivers