Prevent CSRF attacks with CodeIgniter 4

CSRF attacks are relatively common. They rely mainly on the user’s elevated permissions for a certain webapp and the hacker would thus attempt to get the user to perform a certain action on it’s behalf. To cut it short, it’s BAD! How the whole attack works is for sure not the scope of this article. The most common solution though to preventing this kind of attack is to protect your webapp with CSRF tokens (or better yet anti-CSRF tokens).

In this article:

How does CSRF protection work

The concept relies on generating a token (called CSRF token) on the server side which is then checked against a request coming from client’s side. If the token received from the client does not match then the request is deemed invalid and the client will get a 403 response and the intended action is not performed, of course. The prevention method must be used for any type of form submission or ajax request.

Here are the logical steps:

  1. Client lands on a certain page which requires its input in order for an action to be performed (i.e. form submission, be it self or ajax requested)
  2. Server generates CSRF token with it’s corresponding hash
  3. The form needs to be populated with a hidden input containing the csrf token name and the received hash from the server side
  4. Upon form submission, the token and it’s hash value are checked against the previously generated hash.
  5. If the hash matches then the server will continue performing the requested action
  6. If the hash doesn’t match, the server will fail the request with a 403 header. At the same time, the hash is regenerated on server side so the programming logic must reflect an updated of the token on client’s side

Why does this work? It’s pretty simple.. A CSRF attack assumes that the user (with elevated access) has a static way of requesting a state change and the hacker never gains access to the newly generated token since they are not following the proper app logic. This way, the CSRF attack will not be validated and thus the required action / state change will not be performed.

Now let’s move on to how to make this work for you.

Enabling CSRF protection in Codeigniter 4

Like I mentioned in other posts, I am a big fan of CI4. It does provide a lot of enhancements and the security ones, as far as I can tell, are great. One of the features of the framework is that you can easily enable CSRF protection without even knowing much of what goes behind the curtains.

Attention, the following steps can break your webapp logic / functionality so make sure you test in a development environment first.

To enable CSRF protection in CodeIgniter 4 all you need to do is to alter your Environment file:

.env

#--------------------------------------------------------------------
# SECURITY
#--------------------------------------------------------------------

# security.csrfProtection = 'cookie'
# security.tokenRandomize = false
 security.tokenName = 'csrf_token_name'
 security.headerName = 'X-CSRF-TOKEN'
 security.cookieName = 'csrf_cookie_name'
 security.expires = 7200
 security.regenerate = true
# security.redirect = true
# security.samesite = 'Lax'

Then, make sure your Filters configuration include the csrf loading in the “before” definition:

app/Config/Filters.php

class Filters extends BaseConfig
{
    ...
    public $aliases = [
        'csrf'          => CSRF::class,
        'toolbar'       => DebugToolbar::class,
        'honeypot'      => Honeypot::class,
        'invalidchars'  => InvalidChars::class,
        'secureheaders' => SecureHeaders::class,
    ];
    public $globals = [
        'before' => [
            // 'honeypot',
             'csrf',
            // 'invalidchars',
        ],
        'after' => [
            'toolbar',
            // 'honeypot',
            // 'secureheaders',
        ],
    ];

So far, so good. However, if you had any forms on your site (be it in the public or elevated access section) their submission will now no longer function the intended way. If you made these changes on Production environment, CONGRATULATIONS! You just broke your website!

To make sure the functionality is correct, let’s move on to the required logic change.

Changes in your webapp logic

So, remember I mentioned the server (re)generates a hash? They need to be passed on to any page that involve an action that is requestable by the client side.

The two variables that need to be passed on are the token name and the hash. In CodeIgniter 4 you can get them by calling the following functions:

csrf_token(); // will produce the Environment value set by security.tokenName = 'csrf_token_name'
csrf_hash(); // will produce the hash (i.e. 02e41ef3e90243e871d46e753c23304c) 

While the token name remains the same, unless you change it in .env, the hash will always regenerate.

All you need to do now is to pass the result of the two function onto the client side to be added as a hidden input onto the forms.

<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />

If you use the default form action method (i.e. default form submit on its own) then you should be set now since, even if the page reloads with a user error (i.e. bad password on a login form), the newly regenerated hash will be present in the hidden input.

If you submit the form via Ajax (which I believe is the majority of webapps do, for ease of pre-posting functionality and post-response actions), then some minor tweaks are in order.

We will need to update the hidden input has manually upon unsuccessful Ajax return, so that the user can re-post the data and not be declined by the server side on the new request.

I use a little trick on the input field by giving it an ID or better yet assign a dummy class to it (in case there are multiple forms on the page I can thus update the hash for all in one go)

<input type="hidden" class="txt_csrfname" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />

On the server side you must ALWAYS make sure that you return in the Ajax response, the newly generated token:

for example in app/Controllers/Ajax.php

public function example_action(){
 
   // ... perform whatever verifications / validations
    // and, on unsuccesful state change, return the error along with the new token

    $response = [
        'success' => 0,
        'error_message' => 'You have this error in your code',
        'token' => csrf_hash()  // this will get the newly regenerated hash
    ];
    
    return json_encode($response);
 }

All I need to do now on my Ajax request (btw I use jQuery) is to handle the unsuccessful submission of data:

$("#example_form").on('submit',function (e){
	e.preventDefault();

	// ... do whatever client side validation here...
	
	var fd = new FormData(document.getElementById('example_form'));	
	$.ajax({
		type: "POST",
		url: "/ajax/example_action",
		data: fd,
		encode: true,
		dataType: "json",
		contentType: false,
		processData: false
	})
	  .done(function (data) {
		if (!data.success) {

			$(".txt_csrfname").val(data.token)

			// ... do whatever visual updates like error messages ...
 
		} else {
			// ... handle post success actions ...
		}
	   })
	  .fail(function (data) {
		     // ... handle Ajax request failed event ... 
 	   });
});

That’s about all for now. You should be good to go!

More detailed information on CSRF protection can be found in the official CI4 documentation for Security.


Posted

in

, ,

by