CSRF in Grocery CRUD for CodeIgniter 4

Hello,
I used Grocery CRUD with CI3 for many years, but recently I started to use CI4 and I would love to keep using Grocery CRUD there as well :slight_smile:

I have an issue when the form is submitted, I get the error message: “The action you requested is not allowed.” which is shown when the CSRF validation doesn’t work.

In the documentation I found the two functions setCsrfTokenName and setCsrfTokenValue, but they are not found in my crud class, so I guess that they are only for the Enterprise version, while I’m using the community version.

So I’m wondering what’s the best way to disable CSRF only for specific routes (in my case, all the admin ones). I know that I need to do in the filters config file, but I really didn’t understand where I should do it.

Here you can find a copy of my Config/Filters.php file.
From CI4 documentation I know that I can enable CSRF everywhere except for some pages (i.e. all the ones that have an url like “admin/*”), but I didn’t understand in which array I should write it and how (I tried several things that didn’t work).

Thank you in advance for your help! :slight_smile:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\SecureHeaders;

class Filters extends BaseConfig
{
    /**
     * Configures aliases for Filter classes to
     * make reading things nicer and simpler.
     *
     * @var array<string, class-string|list<class-string>> [filter_name => classname]
     *                                                     or [filter_name => [classname1, classname2, ...]]
     */
    public array $aliases = [
        'csrf'          => CSRF::class,
        'toolbar'       => DebugToolbar::class,
        'honeypot'      => Honeypot::class,
        'invalidchars'  => InvalidChars::class,
        'secureheaders' => SecureHeaders::class,
    ];

    /**
     * List of filter aliases that are always
     * applied before and after every request.
     *
     * @var array<string, array<string, array<string, string>>>|array<string, list<string>>
     */
    public array $globals = [
        'before' => [
            // 'honeypot',
            // 'csrf',
            // 'invalidchars',
        ],
        'after' => [
            'toolbar',
            // 'honeypot',
            // 'secureheaders',
        ],
    ];

    /**
     * List of filter aliases that works on a
     * particular HTTP method (GET, POST, etc.).
     *
     * Example:
     * 'post' => ['foo', 'bar']
     *
     * If you use this, you should disable auto-routing because auto-routing
     * permits any HTTP method to access a controller. Accessing the controller
     * with a method you don't expect could bypass the filter.
     */
    public array $methods = [
        'post' => [
            'csrf'
        ]
    ];

    /**
     * List of filter aliases that should run on any
     * before or after URI patterns.
     *
     * Example:
     * 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
     */
    public array $filters = [];
}

Hello @tio ,

If I remember correctly there is a string ‘except’ for Filters so in your case you should do something like this:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Filters\CSRF;

class Filters extends BaseConfig
{
    // ...
    
    public $aliases = [
        // ...
        'csrf'     => CSRF::class,
    ];

    public $globals = [
        'before' => [
            // ...
            'csrf' => ['except' => [
                'admin/*', // Exclude all routes under the "admin" group from CSRF protection
            ]],
        ],
        'after'  => [
            // ...
        ],
    ];

    // ...
}

Let me know if that worked for you :slight_smile:

Thanks for your help, @johnny !
I just tried to do what you said but I keep getting the same error.
I’ll share my fixed Filters class again. Do you notice anything wrong?

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\SecureHeaders;

class Filters extends BaseConfig
{
    /**
     * Configures aliases for Filter classes to
     * make reading things nicer and simpler.
     *
     * @var array<string, class-string|list<class-string>> [filter_name => classname]
     *                                                     or [filter_name => [classname1, classname2, ...]]
     */
    public array $aliases = [
        'csrf'          => CSRF::class,
        'toolbar'       => DebugToolbar::class,
        'honeypot'      => Honeypot::class,
        'invalidchars'  => InvalidChars::class,
        'secureheaders' => SecureHeaders::class,
    ];

    /**
     * List of filter aliases that are always
     * applied before and after every request.
     *
     * @var array<string, array<string, array<string, string>>>|array<string, list<string>>
     */
    public array $globals = [
        'before' => [
            // 'honeypot',
            'csrf' => ['except' => [
                'admin/*' 
            ]],
            // 'invalidchars',
        ],
        'after' => [
            'toolbar',
            // 'honeypot',
            // 'secureheaders',
        ],
    ];

    /**
     * List of filter aliases that works on a
     * particular HTTP method (GET, POST, etc.).
     *
     * Example:
     * 'post' => ['foo', 'bar']
     *
     * If you use this, you should disable auto-routing because auto-routing
     * permits any HTTP method to access a controller. Accessing the controller
     * with a method you don't expect could bypass the filter.
     */
    public array $methods = [
        'post' => [
            'csrf'
        ]
    ];

    /**
     * List of filter aliases that should run on any
     * before or after URI patterns.
     *
     * Example:
     * 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
     */
    public array $filters = [];
}

Hello @tio ,

I guess you need to change the line:

public array $methods = [
        'post' => [
            'csrf'
        ]
    ];

to:

    public array $methods = [
        'post' => [
           'csrf' => ['except' => [
                'admin/*', // Exclude all routes under the "admin" group from CSRF protection
            ]]
        ]
    ];

Let me know if that worked for you.

Regards
Johnny

Not working yet, unfortunately (with the same error message) :sweat_smile:
Any other idea?
Thanks again! :slight_smile:

What is the error now? If it is the same error again then clearly your ‘except’ is not working and csrf is still enabled for your admin pages. You have to double check if the informed path (‘admin/*’) is correct for your application. You can also enable csrf with grocery crud by using (see here):

$crud->setCsrfTokenName(csrf_token());
$crud->setCsrfTokenValue(csrf_hash());

I’ll share here a screenshot. As you can see, the url should be fine (I also tried to add “admin/pages/*” but still not working).
The error message is still “The action you requested is not allowed.”.
If I completely disable the CSRF, it works fine.

I also tried to use the two functions to enable the csrf but I’m afraid that they are only in the enterprise version and not in the latest community version (I get an error when I try to use them). :sweat_smile:

Thank you again!

in

public array $methods = [
        'post' => [
            'csrf'
        ]
    ];

Remove the post csrf part, I believe that makes it run the filter twice, once in globals and once more in methods. You enabled it globally then again you enabled it specifically POST method.
Just have it as

public array $methods = [];

You can check if you have csrf enabled with php spark filter:check post insert_route_here