Struggle with Middleware to do Api-Key auth

Hello

I was using Slim 2 for a long Time and want to use Slim 4 for a new project. I am developing a REST API with a simple X-API-Key header authentication. I struggle because it seems that my POST Request handlers are called event without the API Key is given …

I have tried multiple ways to add the middleware, as callback function or as class with “__invoke()” method, I tried to throw Http Exceptions or create a new Response object … non of it works like I would expect

For me it seems the middleware function is called too late (after processing the request) and not before, what am I doing wrong?

Here some of my code:

app.php:

$app = AppFactory::create();
$app->addBodyParsingMiddleware();
$app->addRoutingMiddleware();
$app->addErrorMiddleware(true, true, true);

$app->group("/api/rest", function (RouteCollectorProxy $group) use ($app) {
    $group->get('/service/ping', [ServiceHealthRestApi::class, "ping"])->add(ApiKeyAuth::class);

    $userController = new UserController($userStorage);
    $group->post('/user',
        function (Request $request, Response $response, array $args = null) use ($userController) {
            $api = new UserRestApi($userController);
            return $api->createUser($request, $response, $args);
        })->add(ApiKeyAuth::class);

ApiKeyAuth.php:

<?php

namespace App;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Exception\HttpForbiddenException;
use Slim\Exception\HttpUnauthorizedException;
use Slim\Psr7\Factory\ResponseFactory;

require_once "config.php";

class ApiKeyAuth
{

    public function __invoke(Request $request, RequestHandler $handler): ResponseInterface
    {
        global $config;

        $apiKey = null;
        $response = $handler->handle($request);
        if (count($request->getHeader(HEADER_X_API_KEY))) {
            $apiKey = $request->getHeader(HEADER_X_API_KEY)[0];
        }
        if (is_null($apiKey) || trim($apiKey) == '') {
            $rf = new ResponseFactory();
            $response = $rf->createResponse(401);
            $response->getBody()->write(json_encode('ERROR_401_API_KEY_MISSING'));
            return $response
                ->withHeader('Content-Type', 'application/json');
            // throw new HttpUnauthorizedException($request, 'ERROR_401_API_KEY_MISSING ' . $oldBody);
        }
        if (!in_array($apiKey, $config->api_keys)) {
            $rf = new ResponseFactory();
            $response = $rf->createResponse(403);
            $response->getBody()->write(json_encode('ERROR_403_API_KEY_MISSING_OR_WRONG'));
            return $response
                ->withHeader('Content-Type', 'application/json');
           // throw new HttpForbiddenException($request, 'ERROR_403_API_KEY_MISSING_OR_WRONG ' . $oldBody);
        }
        return $response;
    }

}

Hi @cs320

For me it seems the middleware function is called too late (after processing the request) and not before, what am I doing wrong?

This depends on where you invoke the handle method in your middleware:

$response = $handler->handle($request);

Inbound-middleware example:

// do something...

$response = $handler->handle($request);

return $response;

Outbound-middleware example:

$response = $handler->handle($request);

// do something...

return $response;

You could also combine the two.

I would guess that you need to insert the code before $response = $handler->handle($request); to handle the request before calling the actual action/controller handler.

A middleware implements the PSR-15 Middleware Interface:

<?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 Slim\Exception\HttpForbiddenException;
use Slim\Exception\HttpUnauthorizedException;

final class ApiKeyAuthMiddleware implements MiddlewareInterface
{
    private $config;

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

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $apiKey = $request->getHeaderLine(HEADER_X_API_KEY);

        if (!$apiKey) {
            throw new HttpUnauthorizedException($request, 'ERROR_401_API_KEY_MISSING');
        }

        if (!in_array($apiKey, $this->config->api_keys)) {
            throw new HttpForbiddenException($request, 'ERROR_403_API_KEY_MISSING_OR_WRONG');
        }

        // Everything is OK
        return $handler->handle($request);
    }

}

@odan thanks that works :slight_smile:

1 Like