Slim3 exclude route from CSRF Middleware

2019-04-02 09:58发布

问题:

I'm building a webshop based on the slim3 framework. I need to handle a server-to-server POST-request to confirm if payment was successful. I added csrf to the container like this:

$container['csrf'] = function($container) {
    return new \Slim\Csrf\Guard;
};

and added it to the app like this:

$app->add($container->csrf);

And it works good. But now i need to be able to add an exception to a certain route so i get the post reques they are sending. I'couldn't find a working solution so far.

Any advice?

回答1:

If you need to exclude one route from a middleware, there are two options:

Option 1: group your routes.

You can group all routes except the one:

<?php
$app->group('', function() {

    // All routes declarations....

})->add($container->csrf); // Add middleware to all routes within the group

// Declare your "exceptional" route outside the group
$app->post('my-special-route-that-has-no-csrf-middleware', 'routeProcessor');

Option 2: use your own middleware

Instead of using \Slim\Csrf\Guard directly, use your own middleware that extends it. Your middleware will check the route, and if route is the "exceptional" one, it will skip.

Add this to settings since you need to access route within middleware:

$container['settings'] => [
    'determineRouteBeforeAppMiddleware' => true
];

Create the middleware extending orginial \Slim\Csrf\Guard:

<?php
class MyCsrfMiddleware extends Slim\Csrf\Guard
{
    // This method is processing every request in your application
    public function processRequest($request, $response, $next) {
        // Check if it's your "exceptional" route
        $route = $request->getAttribute('route');
        if ($route == 'my-special-path') {
            // If it is - just pass request-response to the next callable in chain
            return $next($request, $response);
        } else {
            // else apply __invoke method that you've inherited from \Slim\Csrf\Guard
            return $this($request, $response, $next);
        }
    }
}

/////////////

$container['csrf'] = function($container) {
    return new MyCsrfMiddleware; // Now the container returns your middleware under 'csrf' key
};

Now simply add the middleware to \Slim\App instance:

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


回答2:

Not if anyone is still pulling their hair out about this (especially if you want to use webhooks).

I found a simpler solution with the help of Georgy's answer.

Just make the following modification to the actual Slim\Csrf\Guard 'Guard.php' file and its __invoke method. Or just copy and paste the code below...

public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{

  $route = $request->getAttribute('routeInfo');

  $routeRequestInfo = $route['request'];

  $requestUrl = $routeRequestInfo[1];


  if ($requestUrl == 'http://yoursite/the-url-you-want-to-exempt')
  {
      //This will just return the request to your application with applying the csrf.
      return $next($request, $response);

  }
  else
  {

    $this->validateStorage();

    // Validate POST, PUT, DELETE, PATCH requests
    if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
        $body = $request->getParsedBody();
        $body = $body ? (array)$body : [];
        $name = isset($body[$this->prefix . '_name']) ? $body[$this->prefix . '_name'] : false;
        $value = isset($body[$this->prefix . '_value']) ? $body[$this->prefix . '_value'] : false;
        if (!$name || !$value || !$this->validateToken($name, $value)) {
            // Need to regenerate a new token, as the validateToken removed the current one.
            $request = $this->generateNewToken($request);

            $failureCallable = $this->getFailureCallable();
            return $failureCallable($request, $response, $next);
        }
    }

    // Generate new CSRF token if persistentTokenMode is false, or if a valid keyPair has not yet been stored
    if (!$this->persistentTokenMode || !$this->loadLastKeyPair()) {
        $request = $this->generateNewToken($request);
    }

    // Enforce the storage limit
    $this->enforceStorageLimit();

  }

    return $next($request, $response);
}


回答3:

$container['csrf'] = function($container) {
    $guard = new \Slim\Csrf\Guard;
    $guard->setFailureCallable(function($request, $response, $next) use ($container) {
        $request = $request->withAttribute("csrf_status", false);
        if($request->getAttribute('csrf_status') === false) {
           if($request->getAttribute('route')->getName()=== 'your-route-name'){
            return $next($request, $response);
           }
            return $response->withStatus(400)->withRedirect($container['router']->pathFor('home'));
        } else {
            return $next($request, $response);
        }
    });

    return $guard;
};