joedu12
joedu12

Reputation: 33

Exclude route from Slim CSRF middleware

I'm working on a Slim 3 based application with a Twig frontend and I'm also making a REST API.

I've implemented slimphp\Slim-Csrf for the entire app but I now want to exclude this CSRF check from every "API" routes.

I'm trying to implement the "Option 2" of this post : Slim3 exclude route from CSRF Middleware

Here is the code :

File App\Middleware\CsrfMiddleware.php :

namespace App\Middleware;

class CsrfMiddleware extends \Slim\Csrf\Guard {

    public function processRequest($request, $response, $next) {
        // Check if this route is in the "Whitelist"
        $route = $request->getAttribute('route');

        if ($route->getName() == 'token') {
            var_dump('! problem HERE, this middleware is executed after the CsrfMiddleware !');
            // supposed to SKIP \Slim\Csrf\Guard
            return $next($request, $response);
        } else {
            // supposed to execute \Slim\Csrf\Guard
            return $this($request, $response, $next);
        }
    }
}

File app\app.php :

$app = new \Slim\App([
    'settings' => [
        'determineRouteBeforeAppMiddleware' => true
    ]
]);

require('container.php');
require('routes.php');

$app->add($container->csrf);
$app->add('csrf:processRequest');

File app\container.php :

$container['csrf'] = function ($container) {
    return new App\Middleware\CsrfMiddleware;
};

File app\routes.php :

<?php
$app->get('/', \App\PagesControllers\LieuController::class.':home')->setName('home');

$app->post('/api/token', \App\ApiControllers\AuthController::class.'postToken')->setName('token');

When I do a POST request on http://localhost/slim3/public/api/token I've got :

Failed CSRF check!string(70) "! problem HERE, this middleware is executed after the CsrfMiddleware !"

Like if my CsrfMiddleware was executed after \Slim\Csrf\Guard...

Anyone has an idea ?

Thank you.

Upvotes: 3

Views: 1465

Answers (2)

icefield
icefield

Reputation: 193

Here is how I achieved this with Slim 3.

1) Create a class that extends \Slim\Csrf\Guard as follows.

The CsrfGuardOverride class is key to enabling or disabling CSRF checking for a path. If the current path is whitelist'ed, then the __invoke() method just skips the core CSRF checking, and proceeds by executing the next middleware layer.

If the current path is not in the whitelist (i.e., CSRF should be checked), then the __invoke method defers to its parent \Slim\Csrf\Guard::__invoke() to handle CSRF in the normal manner.

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use \Slim\Csrf\Guard;

class CsrfGuardOverride extends Guard {

    /**
     * Invoke middleware
     *
     * @param  ServerRequestInterface  $request  PSR7 request object
     * @param  ResponseInterface $response PSR7 response object
     * @param  callable          $next     Next middleware callable
     *
     * @return ResponseInterface PSR7 response object
     */
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
    {
        // Set the name of the route we want whitelisted with a name
        // prefix of 'whitelist'.  check for that here, and add
        // any path to the white list
        $route = $request->getAttribute('route');
        $routeName = $route->getName();
        $whitelisted = strpos($routeName, 'whitelist');

        // if url is whitelisted from being CSRF checked, then bypass checking by skipping directly to next middleware
        if ($whitelisted !== FALSE) {
            return $next($request, $response);
        }

        return parent::__invoke($request, $response, $next);
    }

}

2) Register the CsrfGuardOverride class. Be sure to set settings.determineRouteBeforeAppMiddleware => true as this forces Slim to evaluate routes prior to executing any middleware.

    // Method on App Class
    protected function configureContainer(ContainerBuilder $builder)
    {
        parent::configureContainer($builder);

        $definitions = [
            'settings.displayErrorDetails' => true,
            'settings.determineRouteBeforeAppMiddleware' => true,

            // Cross-Site Request Forgery protection
            \App\Middleware\CsrfGuardOverride::class => function (ContainerInterface $container) {
                $guard = new \App\Middleware\CsrfGuardOverride;
                $guard->setPersistentTokenMode(true);   // allow same CSRF token for multiple ajax calls per session
                return $guard;
            },
            'csrf' => DI\get(\App\Middleware\CsrfGuardOverride::class),

            // add others here...
        ];

        $builder->addDefinitions($definitions);
    }

3) Add the path that you want to by bypass CSRF checking and give it a name with the prefix 'whitelist':

$app->post('/events/purchase', ['\App\Controllers\PurchaseController', 'purchaseCallback'])->setName('whitelist.events.purchase');

Upvotes: 0

odan
odan

Reputation: 4962

In Slim 3 the middleware is LIFO (last in first out). Add the middleware in the opposite direction:

Before

$app->add($container->csrf);
$app->add('csrf:processRequest');

After

$app->add('csrf:processRequest');
$app->add($container->csrf);

Notice: The public directory should not be part of the url

Not correct: http://localhost/slim3/public/api/token

Correct: http://localhost/slim3/api/token

To skip the processing within the middleware, just return the $response object.

// supposed to SKIP \Slim\Csrf\Guard
return $response;

Upvotes: 1

Related Questions