How to Send AJAX request with CSRF token in CodeIgniter 4

How to Send AJAX request with CSRF token in CodeIgniter 4

 

Cross-Site Request Forgery (CSRF) requests are a type of malicious exploit whereby unauthorized commands are performed on behalf of an authenticated user.

CodeIgniter 4 provides protection from CSRF attacks. But it is not enabled by default same as CodeIgniter 3.

The token is generated for each user and it is managed by CodeIgniter to verify the user request.

In this tutorial, I show how you can enable CSRF protection and send AJAX request with CSRF token in the CodeIgniter 4 project.


 1. Database configuration

  • Open .env file which is available at the project root.

NOTE – If dot (.) not added at the start then rename the file to .env.

  • Remove # from start of database.default.hostname, database.default.database, database.default.username, database.default.password, and database.default.DBDriver.
  • Update the configuration and save it.
database.default.hostname = 127.0.0.1
database.default.database = testdb
database.default.username = root
database.default.password = 
database.default.DBDriver = MySQLi

2. Enable CSRF

  • Again open .env file.
  • Remove # from the start of the security.tokenName,security.headerNamesecurity.cookieNamesecurity.expiressecurity.regenerate, and security.samesite.
  • I update the security.tokenName value with 'csrf_hash_name'. With this name read CSRF hash. You can update it with any other value.
  • If you don’t want to regenerate CSRF hash after each AJAX request then set security.regenerate = false.
security.tokenName = 'csrf_hash_name' 
security.headerName = 'X-CSRF-TOKEN' 
security.cookieName = 'csrf_cookie_name' 
security.expires = 7200 
security.regenerate = true 
security.redirect = true 
security.samesite = 'Lax'
  • Open app/Config/Filters.php file.
  • Uncomment 'csrf' in 'before' if commented.
// Always applied before every request
public $globals = [
    'before' => [
       //'honeypot'
       'csrf',
    ],
    'after' => [
       'toolbar',
       //'honeypot'
    ],
];

3. Create Table

  • Create a new table users using migration.
php spark migrate:create create_users_table
  • Now, navigate to app/Database/Migrations/ folder from the project root.
  • Find a PHP file that ends with create_users_table and open it.
  • Define the table structure in the up() method.
  • Using the down() method delete users table which calls when undoing migration.
<?php namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateUsersTable extends Migration
{
    public function up() {
       $this->forge->addField([
          'id' => [
             'type' => 'INT',
             'constraint' => 5,
             'unsigned' => true,
             'auto_increment' => true,
          ],
          'name' => [ 
             'type' => 'VARCHAR',
             'constraint' => '100',
          ],
          'username' => [
             'type' => 'VARCHAR',
             'constraint' => '80',
          ],
          'gender' => [
             'type' => 'VARCHAR',
             'constraint' => '10',
          ],
          'email' => [
             'type' => 'VARCHAR',
             'constraint' => '80',
          ],

       ]);
       $this->forge->addKey('id', true);
       $this->forge->createTable('users');
    }

    //--------------------------------------------------------------------

    public function down() {
       $this->forge->dropTable('users');
    }
}
  • Run the migration –
php spark migrate

4. Model

  • Create Users Model –
php spark make:model Users
  • Open app/Models/Users.php file.
  • In $allowedFields Array specify field names – ['name','username','gender','email'] that can be set during insert and update.

Completed Code

<?php

namespace App\Models;

use CodeIgniter\Model;

class Users extends Model
{
     protected $DBGroup = 'default';
     protected $table = 'users';
     protected $primaryKey = 'id';
     protected $useAutoIncrement = true;
     protected $insertID = 0;
     protected $returnType = 'array';
     protected $useSoftDeletes = false;
     protected $protectFields = true;
     protected $allowedFields = ['name','username','gender','email'];

     // Dates
     protected $useTimestamps = false;
     protected $dateFormat = 'datetime';
     protected $createdField = 'created_at';
     protected $updatedField = 'updated_at';
     protected $deletedField = 'deleted_at';

     // Validation
     protected $validationRules = [];
     protected $validationMessages = [];
     protected $skipValidation = false;
     protected $cleanValidationRules = true;

     // Callbacks
     protected $allowCallbacks = true;
     protected $beforeInsert = [];
     protected $afterInsert = [];
     protected $beforeUpdate = [];
     protected $afterUpdate = [];
     protected $beforeFind = [];
     protected $afterFind = [];
     protected $beforeDelete = [];
     protected $afterDelete = [];
}

5. Route

  • Open app/Config/Routes.php file.
  • Define 2 routes –
    • / – Display users view.
    • users/userDetails – It is used for sending AJAX request.

Completed Code

$routes->get('/', 'UsersController::index');
$routes->post('users/userDetails', 'UsersController::userDetails');

6. Controller

  • Create UsersController Controller –
php spark make:controller UsersController
  • Open app/Controllers/UsersController.php file.
  • Import Users Model.
  • Create 2 methods –
    • index() – Fetch all records from the users table and assign to the $data['users']. Load users view and pass $data Array.
    • userDetails() – This method is use to handle AJAX request. With this method return user data in JSON format by username.

Read POST values and assign in $postData variable. Create $data Array to store return response. Assign new CSRF token to $data['token'].

Define validation. If the POST value is not valid then assign 0 to $data['success'] and error message to $data['error'].

If the value is valid then assign 1 to $data['success'] and fetch record by username from users table. Assign the fetched record to $data['user'].

Return $data Array in JSON format.

Completed Code

<?php 
namespace App\Controllers;

use App\Controllers\BaseController; 
use App\Models\Users; 

class UsersController extends BaseController {

   public function index(){
      $users = new Users();

      ## Fetch all records
      $data['users'] = $users->findAll();

      return view('users',$data);
   }

   public function userDetails(){

      $request = service('request');
      $postData = $request->getPost();

      $data = array();

      // Read new token and assign in $data['token']
      $data['token'] = csrf_hash();

      ## Validation
      $validation = \Config\Services::validation();

      $input = $validation->setRules([
        'username' => 'required|min_length[3]'
      ]);

      if ($validation->withRequest($this->request)->run() == FALSE){

         $data['success'] = 0;
         $data['error'] = $validation->getError('username');// Error response

      }else{

         $data['success'] = 1;
         
         // Fetch record
         $users = new Users();
         $user = $users->select('*')
                ->where('username',$postData['username'])
                ->findAll();

         $data['user'] = $user;

      }

      return $this->response->setJSON($data);

   }

}

7. View

Create users.php file in app/Views/.

Create a hidden element to store CSRF token name specified in .env file in the name attribute and store CSRF hash in the value attribute.

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

Add user names in the <select id='sel_user' > by looping on the $users. Store $user['username'] in the value attribute.

To display selected user details created <span > elements.

Script –

Define the change event on the #sel_user.

Read CSRF Token name and hash from the hidden field and assign it to the csrfName and csrfHash.

Assign selected option value to username variable.

Send AJAX POST request to "<?=site_url('users/userDetails')?>". Pass username and CSRF hash as data – {username:username,[csrfName]:csrfHash } as data.

Here, hash will pass like – [csrfName]: csrfHash.

Set dataType: 'json'. On successful callback assign response.token to '.txt_csrfname'. Empty the <span > elements.

If response.success == 1 then loop on the response.user to read user data. Update <span > text value with user data.

If response.success does not equal to 0 then alert response.error.

Completed Code

<!doctype html>
<html>
<head>
   <title>How to Send AJAX request with CSRF token in CodeIgniter 4</title>
</head>
<body>

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

   <br>
   Select Username : <select id='sel_user'>
   <?php 
   foreach($users as $user){
   ?>
      <option value='<?= $user["username"]?>'><?= $user["name"] ?></option>
   <?php
   }
   ?>
   </select>

   <!-- User details -->
   <div >
     Username : <span id='suname'></span><br/>
     Name : <span id='sname'></span><br/>
     Email : <span id='semail'></span><br/>
   </div>

   <!-- Script -->
   <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
   $(document).ready(function(){

     $('#sel_user').change(function(){

       // CSRF Hash
       var csrfName = $('.txt_csrfname').attr('name'); // CSRF Token name
       var csrfHash = $('.txt_csrfname').val(); // CSRF hash

       // Username
       var username = $(this).val();

       // AJAX request
       $.ajax({
          url: "<?=site_url('users/userDetails')?>",
          method: 'post',
          data: {username: username,[csrfName]: csrfHash },
          dataType: 'json',
          success: function(response){

            // Update CSRF hash
            $('.txt_csrfname').val(response.token);

            // Empty the elements
            $('#suname,#sname,#semail').text('');

            if(response.success == 1){
               // Loop on response
               $(response.user).each(function(key,value){

                  var uname = value.username;
                  var name = value.name;
                  var email = value.email;

                  $('#suname').text(uname);
                  $('#sname').text(name);
                  $('#semail').text(email);
               });
            }else{
               // Error
               alert(response.error);
            }

          }
       });
     });
   });
   </script>
</body>
</html>

8. Run

  • Navigate to the project, and
  • Execute “php spark serve” command.
php spark serve
  • Run http://localhost:8080 in the web browser.

9. Demo

View Demo


10. Conclusion

CSRF token not required to send with AJAX GET request. If you have enabled the CSRF token regenerate then you need to update the token after each request as I do in the example.

Set app.CSRFRegenerate = false if you want to use the same token for all AJAX calls.

You can view this tutorial to know how to send an AJAX request with CSRF token in CodeIgniter 3.



Comments