Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cakephp 3.5.6 disable CSRF Middleware for controller

I'm trying to disable the CSRF check for a single controller (API), but I'm unable to find how I'm able to achieve this.

The pre 3.5.0 CSRF Component had the ability to be disabled on certain request using:

$this->eventManager()->off($this->Csrf);
like image 439
Robin.v Avatar asked Dec 08 '17 13:12

Robin.v


1 Answers

There are two ways to do that.

Apply the middleware to specific routing scopes (or even routes)

Depending on what routes you create, you can apply the middleware to specific scopes only, for example:

// config/routes.php

use Cake\Http\Middleware\CsrfProtectionMiddleware;

Router::scope('/', function ($routes) {
    $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware([
        'httpOnly' => true
    ]));

    $routes->scope('/api', function ($routes) {
        // ...
    });

    $routes->scope('/blog', function ($routes) {
        $routes->applyMiddleware('csrf');
        // ...
    });

    $routes->scope('/cms', function ($routes) {
        $routes->applyMiddleware('csrf');
        // ...
    });
});

This would apply the CSRF middleware only to the routes connected in the blog and cms scopes.

It's also possible to narrow things down further to route level, and apply the middleware on specific routes:

$routes
    ->connect('/blog/:action', ['controller' => 'Blogs'])
    ->setMiddleware(['csrf']);

This would apply the CSRF middleware to only the /blog/* routes.

Conditionally apply the middleware "manually"

Another way would be to manually the apply the middleware when applicable. As of CakePHP 3.8, the middleware has a whitelisting method named whitelistCallback, inside of it you can check the request object and return a boolean that defines whether the checks are applied:

// src/Application.php

// ...
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Psr\Http\Message\ServerRequestInterface;

class Application extends BaseApplication
{
    // ...

    public function middleware($middleware)
    {
        $csrf = new CsrfProtectionMiddleware([
            'httpOnly' => true
        ]);
        $csrf->whitelistCallback(function (ServerRequestInterface $request) {
            $params = $request->getAttribute('params');
            return $params['controller'] !== 'Api';
        });

        $middleware
            // ...
            ->add(new RoutingMiddleware())

            ->add($csrf);

        return $middleware;
    }
}

In earlier CakePHP versions you'll have to create a custom middleware handler so that you can access the current request object, from which you can extract the controller parameter, and then you have to invoke the CSRF middleware inside your handler, something along the lines of this:

// src/Application.php

// ...
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class Application extends BaseApplication
{
    // ...

    public function middleware($middleware)
    {
        $middleware
            // ...
            ->add(new RoutingMiddleware())

            ->add(function (
                ServerRequestInterface $request,
                ResponseInterface $response,
                callable $next
            ) {
                $params = $request->getAttribute('params');
                if ($params['controller'] !== 'Api') {
                    $csrf = new CsrfProtectionMiddleware([
                        'httpOnly' => true
                    ]);

                    // This will invoke the CSRF middleware's `__invoke()` handler,
                    // just like it would when being registered via `add()`.
                    return $csrf($request, $response, $next);
                }

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

        return $middleware;
    }
}

Note that in both cases you must put the middleware after the routing middleware, as that is where the controller information is being set.

If applicable, you could also test against the request URL instead of the routing paramteres, for example something like this:

if (mb_strpos($request->getUri()->getPath(), '/api/') === false) {
    // apply CSRF checks
}

When doing so, the middleware wouldn't be restricted to be placed after the routing middleware, you could theoretically place it at whichever position you want.

See also

  • Cookbook > Middleware > Cross Site Request Forgery (CSRF) Middleware
  • Cookbook > Routing > Connecting Scoped Middleware
  • Cookbook > Middleware > Creating Middleware
like image 74
ndm Avatar answered Oct 13 '22 20:10

ndm