CodeIgniter 4 Filters Example

Introduction

In this tutorial, I will discuss about how to work with CodeIgniter 4 filters. These filters are controller filters.

Controller Filters allow you to perform actions either before or after the controllers execute. Unlike events, you can choose the specific URIs in which the filters will be applied to. Incoming filters may modify the Request while after filters can act on and even modify the Response, allowing for a lot of flexibility and power. Some common examples of tasks that might be performed with filters are:

  • Performing CSRF protection on the incoming requests
  • Restricting areas of your site based upon their Role
  • Perform rate limiting on certain endpoints
  • Display a “Down for Maintenance” page
  • Perform automatic content negotiation
codeigniter 4 filters

Creating a Filter

Filters are simple classes that implement CodeIgniter\Filters\FilterInterface. They contain two methods: before() and after() which hold the code that will run before and after the controller respectively. Your class must contain both methods but may leave the methods empty if they are not needed. A skeleton filter class looks like:

<?php

namespace App\Filters;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

class MyFilter implements FilterInterface {
    public function before(RequestInterface $request, $arguments = null)    {
        // Do something here
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)   {
        // Do something here
    }
}

Before Filters

From any filter, you can return the $request object and it will replace the current Request, allowing you to make changes that will still be present when the controller executes.

Since before filters are executed prior to your controller being executed, you may at times want to stop the actions in the controller from happening. Also, when you have a series of filters you may also want to stop the execution of the later filters after a certain filter. You can easily do this by returning any non-empty result. If the before filter returns an empty result, the controller actions or the later filters will still be executed. An exception to the non-empty result rule is the Request instance. Returning it in the before filter will not stop the execution but only replace the current $request object.

This is typically used to perform redirects, like in this example:

public function before(RequestInterface $request, $arguments = null) {
    $auth = service('auth');

    if (! $auth->isLoggedIn()) {
        return redirect()->to(site_url('login'));
    }
}

If a Response instance is returned, the Response will be sent back to the client and script execution will stop. This can be useful for implementing rate limiting for APIs. See Throttler for an example.

After Filters

After filters are nearly identical to before filters, except that you can only return the $response object, and you cannot stop script execution. This does allow you to modify the final output, or simply do something with the final output. This could be used to ensure certain security headers were set the correct way, or to cache the final output, or even to filter the final output with a bad words filter.

Configuring Filters

Once you’ve created your filters, you need to configure when they get run. This is done in app/Config/Filters.php. This file contains four properties that allow you to configure exactly when the filters run.

$aliases

The $aliases array is used to associate a simple name with one or more fully-qualified class names that are the filters to run:

public $aliases = [
    'csrf' => \CodeIgniter\Filters\CSRF::class,
];

Aliases are mandatory and if you try to use a full class name later, the system will throw an error. Defining them in this way makes it simple to switch out the class used. Great for when you decided you need to change to a different authentication system since you only change the filter’s class and you’re done.

You can combine multiple filters into one alias, making complex sets of filters simple to apply:

public $aliases = [
    'apiPrep' => [
        \App\Filters\Negotiate::class,
        \App\Filters\ApiAuth::class,
    ]
];

You should define as many aliases as you need.

$globals

The second section allows you to define any filters that should be applied to every request made by the framework. You should take care with how many you use here, since it could have performance implications to have too many run on every request. Filters can be specified by adding their alias to either the before or after array:

public $globals = [
    'before' => [
        'csrf',
    ],
    'after' => [],
];

There are times where you want to apply a filter to almost every request, but have a few that should be left alone. One common example is if you need to exclude a few URI’s from the CSRF protection filter to allow requests from third-party websites to hit one or two specific URI’s, while keeping the rest of them protected. To do this, add an array with the ‘except’ key and a uri to match as the value alongside the alias:

public $globals = [
    'before' => [
        'csrf' => ['except' => 'api/*'],
    ],
    'after' => [],
];

Any place you can use a URI in the filter settings, you can use a regular expression or, like in this example, use an asterisk for a wildcard that will match all characters after that. In this example, any URL’s starting with api/ would be exempted from CSRF protection, but the site’s forms would all be protected. If you need to specify multiple URI’s you can use an array of URI patterns:

public $globals = [
    'before' => [
        'csrf' => ['except' => ['foo/*', 'bar/*']],
    ],
    'after' => [],
];

$methods

You can apply filters to all requests of a certain HTTP method, like POST, GET, PUT, etc. In this array, you would specify the method name in lowercase. It’s value would be an array of filters to run. Unlike the $globals or the $filters properties, these will only run as before filters:

public $methods = [
    'post' => ['foo', 'bar'],
    'get'  => ['baz'],
]

In addition to the standard HTTP methods, this also supports one special case: ‘cli’. The ‘cli’ method would apply to all requests that were run from the command line.

$filters

This property is an array of filter aliases. For each alias, you can specify before and after arrays that contain a list of URI patterns that filter should apply to:

public filters = [
    'foo' => ['before' => ['admin/*'], 'after' => ['users/*']],
    'bar' => ['before' => ['api/*', 'admin/*']],
];

Filter arguments

When configuring filters, additional arguments may be passed to a filter when setting up the route:

$routes->add('users/delete/(:segment)', 'AdminController::index', ['filter' => 'admin-auth:dual,noreturn']);

In this example, the array [‘dual’, ‘noreturn’] will be passed in $arguments to the filter’s before() and after() implementation methods.

Provided Filters

Three filters are bundled with CodeIgniter4: Honeypot, CSRF, and DebugToolbar.

CodeIgniter 4 Filter Example

Let’s talk about the example I am going to implement here. In my tutorial about CodeIgniter 4 Login Logout, I had shown how to implement login and logout functionality. I had also shown how to use CodeIgniter session in login functionality. In this login or authentication application I had checked in controller methods whether a user is already logged in or not and accordingly redirect to the appropriate page. Now I am going to check whether a user is logged in or not in the filter instead of controller.

Keeping this condition checked in filter has few benefits:

  • Your condition is put in one place and hence the code is clean.
  • If you need to change the condition in future, then you have to change in one place.

Create Filter – AuthFilter

Create a filter class AuthFilter in a file app/Filters/AuthFilter.php with the following code:

<?php

namespace App\Filters;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

use App\Libraries\AuthLibrary;

class AuthFilter implements FilterInterface {
	
    public function before(RequestInterface $request, $arguments = null) {
        $authLib = new AuthLibrary();
		
		//echo 'Before Filter';
		
		if (!$authLib->is_logged_in()) {
            return redirect()->to(site_url('auth/login'));
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) {
        //echo 'After Filter';
		//print_r ($response);
    }
	
}

I have loaded AuthLibrary (use App\Libraries\AuthLibrary;) to use the function for checking condition whether user has already been logged in or not.

I am checking whether a user is authenticated or not in the before() function:

public function before(RequestInterface $request, $arguments = null) {
	$authLib = new AuthLibrary();
	
	//echo 'Before Filter';
	
	if (!$authLib->is_logged_in()) {
		return redirect()->to(site_url('auth/login'));
	}
}

Configure Filter

Open the file app/Config/Filter.php and make the following update to include AuthFilter:

<?php

namespace Config;

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

use App\Filters\AuthFilter;

class Filters extends BaseConfig
{
    /**
     * Configures aliases for Filter classes to
     * make reading things nicer and simpler.
     *
     * @var array
     */
    public $aliases = [
        'csrf'     => CSRF::class,
        'toolbar'  => DebugToolbar::class,
        'honeypot' => Honeypot::class,
        'authFilter' => AuthFilter::class,
    ];
    …

I have added use App\Filters\AuthFilter; before the class declaration of Filter.

I have also added 'authFilter' => AuthFilter::class, in the public $aliases = []. There is no more change because I want to apply filter on routes, so I will make changes in the app/Config/Routes.php file.

Configure Routes.php

Open file app/Config/Routes.php file and replace $routes->get('/', 'Auth::index'); by $routes->get('/', 'Auth::index', ['filter' => 'authFilter']);. This is called filter on single route.

If you want to apply filter on multiple routes then you can, for example, use the following syntax:

$routes->group('auth', ['filter' => 'authFilter'], function($routes) {
    $routes->get('/', 'Auth::index');
    $routes->get('logout', 'Auth::logout');
});

Clean Controller Code

Now I will remove unnecessary code from controller (app/Controllers/Auth.php) functions.

In the index() function, replace the following code snippets:

if (!$this->authLib->is_logged_in()) {
	return redirect()->to(site_url('auth/login'));
} else {
	echo view('home');
}

By

echo view('home');

In the logout() function, replace the following code snippets:

if ($this->authLib->is_logged_in()) {
    $this->authLib->logout();
}

By

$this->authLib->logout();

The rest of the code remains same from Codeigniter 4 login logout example.

Deploying the Filter Application

I am not going to use any external server but CLI command to run the application. Make sure you start the MySQL database server before you start your application. If you want to use external server to run your application you can use. Execute the following command on your project root directory to run your application.

php spark serve

Your application will be running on localhost and port 8080.

Testing Filters in Codeigniter 4

Now when you browse the URL http://localhost:8080 in the browser, you will be redirected to the login page. Once you use credentials user/user to login, you will see the dashboard area. From the dashboard area you can logout and you will be redirected to the login page again.

Hope you got an idea how to use filters in CodeIgniter 4.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *