Hi, in my Slim 4 app I use middleware on all routes (e.g. Routing Middleware) which is set last (therefore executed first) and some middlewares on single routes. It seems the app code is executed before the second middleware (the ‘SlimMiddlewareRequestHeadersCheck’), indicated by an error thrown by Guzzle before the middleware should return a 403. If I delete the relevant code for Guzzle a 403 is thrown, indicatin to me, the second middleware runs after the app code. Why and how can I have it run before?
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
final class SlimMiddlewareRequestHeadersCheck {
public function __invoke(Request $request, RequestHandler $handler) : Response {
$response = $handler->handle($request);
...
if($xRequestedWithHeader === null || $xRequestedWithHeader != 'xmlhttprequest') {
$response = new \Slim\Psr7\Response();
$response = $response->withStatus(403); //forbidden
}
...
return $response;
}
}
Thanks. But isn’t it the same as my code (I have the $reponse = $handler->handle($request) at the top and if the If-Block is not executed this $response is returned)?
Maybe I misunderstand how middleware works. I assume if Middleware BEFORE route closure is excuted and returns a new response with error code, the route closure is not even touched or is?
It seems the app code is executed before the second middleware
Calling $handler->handle($request) before your code will run the rest of the middleware stack and the route handler (think of the route handler as at the end of the middleware stack).
So if you want your code to run first you need to place it before this statement. If you want your code to run after, place your code after it.
I am bit confused now. I thought the onion layer structure means this: middleware1 → middleware2 → app → middleware2 → middleware1
And if any middleware creates a new Response with 4xx status code and returns it, the next middleware and app are not reached.
Everything before this line is executed before the last middleware (route dispatcher) calls the action handler. Ingoing middleware.
Everything after this line is being executed after the last middleware (route dispatcher) has called your action handler. Outgoing middleware.
There can also be a mix of both types.
The middleware order in Slim is LIFO, so the last middleware will be executed first. In the most cases this is the Slim ErrorHandler middleware.
And if any middleware creates a new Response with 4xx status code and returns it, the next middleware and app are not reached.
This is correct. When you don’t invoke the handlers handle method you have to create and return your own Response object. Then only the outgoing middlewares will handle this response object.
I did not manage to get it working without checking for an error status code from a middleware executed before (return $response->withStatus(403) does not stop the execution of next middleware). What am I doing wrong?
Returning another response object does not stop the middleware stack. The outgoing middlewares will be processed anyway.
To stop the execution, you could throw an Exception instead. The Slim ErrorMiddleware is then able to catch and render it into an HTML or JSON response.
For the status code 403 you could throw a HttpForbiddenException.
Replace this:
$response = new \Slim\Psr7\Response();
$response = $response->withStatus(403);
with this:
throw new \Slim\Exception\HttpForbiddenException();
There are even more HTTP specific Exception classes:
I recognized that Slim always returns 200 for an OPTIONS request, independent of any thrown exception. Is there a recommended way on dealing with missing or not-allowed origins in a preflight request? I would throw 400 and 403 respectively (but the 200 is returned as mentioned). Mozilla (Access-Control-Allow-Origin - HTTP | MDN) says nothing about it and for security reasons recommends to avoid “null” for the Access-Control-Allow-Origin (ACAO) header. Best idea so far: send response to preflight without the ACAO header when origin is missinn or not allowed
Yes, but this should only be the case when the lazy CORS route ($app->options('/{routes:.+}' is enabled. Is this how it is implemented in your application?
The $regexToExcludeFromAllRoutes includes the routes for which I already have an OPTIONS method implemented. The catch-all OPTIONS route is the second last in my code, just before a catch-all any: any(’/routes:.+}’
On both implementations (single and catch-all OPTIONS route) a 200 is returned for missing or not-allowed origin and the error (400 or 403) is sent in body with some error message