Search code examples
phpajaxcodeigniter-3

CSRF Token Regeneration Issue in CodeIgniter AJAX Form Submissions


I'm working on a CodeIgniter application where I'm using CSRF protection with AJAX form submissions. I have $config['csrf_regenerate'] = TRUE; enabled in my configuration, but I'm encountering a 403 error on repeated form submissions.

  1. I'm fetching a new CSRF token on every form submission using security->get_csrf_hash(); ?> in my JavaScript code.
  2. I'm sending the token name and hash along with the form data in the AJAX request.
  3. I'm assuming the tokens are being regenerated on the server-side with $config['csrf_regenerate'] = TRUE; in my config.php.

Problem:

  1. I'm still getting a 403 error when submitting the form multiple times. I suspect there might be a mismatch between the expected and received token.

I'm looking for guidance on how to best handle CSRF protection and token regeneration in my scenario.

Form

<form id="add_category_form" method="post">
                    <h2>Add a Category</h2>
                    <ul>
                        <li>
                            <input type="text" name="category_name" required>
                            <label>Category Name</label>
                        </li>
                        <li>
                            <textarea name="description" required></textarea>
                            <label>Description</label>
                        </li>
                        <!-- FIXME: center the content of this last li tag -->
                        <!--  <li>
                            <label>Upload Images (5 Max)</label>
                            <input type="file" name="image" accept="image/*">
                        </li>
                    </ul> -->
                        <button type="button" data-dismiss="modal" aria-label="Close">Cancel</button>
                        <button type="submit">Save</button>
                </form>

Jquery

<script>
    $(document).ready(function() {
        $("#add_category_form").on("submit", function(e) {
            e.preventDefault();

            let csrfTokenName = '<?= $this->security->get_csrf_token_name(); ?>';
            let csrfHash = '<?= $this->security->get_csrf_hash(); ?>'; // Assuming you have these values in your view

            let form_data = $(this).serialize() + "&" + csrfTokenName + "=" + csrfHash;

            $.post("<?= base_url('CategoriesController/process_add_category'); ?>", form_data, function(response) {
                    console.log(response);
                })
                .fail(function(jqXHR, textStatus, errorThrown) {
                    console.error("AJAX Error:", textStatus, errorThrown);
                });

            return false;
        });
    });
</script>

Controller method

public function process_add_category() {
    $category_name = $this->input->post("category_name");
    echo $category_name;
}

Output output


Solution

  • This problem is now solved. I resolve this by setting the newly returned CSRF token from the server to the form's CSRF token so that everytime I submit a form, my form's token and server's token will always same and avoid CSRF token mismatch.

    Form

    <form id="add_category_form" action="<?= base_url("CategoriesController/process_add_category") ?>" method="post">
                        <input type="hidden" name="<?= $this->security->get_csrf_token_name() ?>" value="<?= $this->security->get_csrf_hash() ?>" />
                        <h2>Add a Category</h2>
                        <ul>
                            <li>
                                <input type="text" name="category_name" required>
                                <label>Category Name</label>
                            </li>
                            <li>
                                <textarea name="description" required></textarea>
                                <label>Description</label>
                            </li>
                            <label>Upload Images (5 Max)</label>
                            <!--  <ul>
                                    <li><button type="button" class="upload_image"></button></li>
                                </ul> -->
                            <input type="file" name="image" accept="image/*">
                        </ul>
                        <!-- FIXME: center the content of this last li tag -->
                        <button type="button" data-dismiss="modal" aria-label="Close">Cancel</button>
                        <button type="submit">Save</button>
                    </form>
    

    JavaScript

    $(document).ready(function() {
            $("#add_category_form").submit(function(e) {
                e.preventDefault();
                let url = $(this).attr("action");
                let formData = $(this).serialize();
                console.log(formData);
    
                $.post(url, formData, function(response) {
                    console.log(response);
                    /* Set the returned newly created hash and name to the form's token name and value*/
                    csrfName = response.csrfName;
                    $("input[name='<?= $this->security->get_csrf_token_name() ?>']").val(response.newCsrfToken);
                }, "json");
                return false;
            });
        });
    

    Controller

    public function process_add_category()
    {
        $response = array(
            "message" => "Added category successfully!",
            "csrfName" => $this->security->get_csrf_token_name(),
            "newCsrfToken" => $this->security->get_csrf_hash()
        );
        echo json_encode($response);
    }