简体   繁体   中英

Codeigniter 3 multiple forms with ajax and csrf tokens working on one form only

I have admin page with multiple settings, each setting have different form in different tab.

I am using ajax and to save data, and i didn't had any problems so far with csrf token when i had only one form on a page, or when i disable csrf token.

On each ajax request new token is generated in controller and sent back to ajax which is updating hidden field with name="csrf_token" but with different id's.

After first form is submitted all is good, but when i try to submit other form csrf token doesn't work anymore i am getting message "The action you have requested is not allowed." with page 403 in console output even after i reload page and try to submit other form that didn't worked.

Is there way to have multiple forms with csrf protection on same page and how to handle that?

Here is code examples Forms with ajax

<form id="upload-icon" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="csrf_token" id="csrf_token_1" value="<?php echo $this->security->get_csrf_hash(); ?>">
    <input type="file" id="favicon_image" name="favicon_image" accept=".ico">
    <button type="button" id="upload-icon-btn">Upload</button>
</form>

<form id="update-settings" method="POST">
    <input type="hidden" name="csrf_token" id="csrf_token_2" value="<?php echo $this->security->get_csrf_hash(); ?>">
    <input type="text" name="settings_one">
    <input type="text" name="settings_two">
    <input type="text" name="settings_three">
    <button type="button" id="update-settings-btn">Update settings</button>
</form>

<script>
$(document).ready(function() {
    var csrf_token = '';
    // upload favicon form
    $('#upload-favicon-form-btn').on('click', function(e) {
        e.preventDefault();

        var fd = new FormData();
        var files = $('#favicon_image')[0].files[0];
        fd.append('favicon_image', files);

        var favicon = $('#favicon_image').val();
        if (favicon == '') {
            loadModal('Warning', 'Please select <strong>favicon.ico</strong> icon file.');
        } else {
            $.ajax({
                type: 'POST',
                url: '<?php echo base_url('admin/settings/upload_ico'); ?>',
                data: fd,
                contentType: false,
                cache: false,
                processData: false,
                dataType: 'json',
                success: function(response) {
                    csrf_token = response.csrf_token;
                    $('#csrf_token_1').val(csrf_token);

                    // messages output
                },
                error: function() {
                    // error message output
                }
            });
        }
    });

    // update settings form
    $('#update-settings-btn').on('click', function(e) {
        e.preventDefault();
        $.ajax({
            type: 'POST',
            url: '<?php echo base_url('admin/settings/update_settings'); ?>',
            data: $('#update-settings').serialize(),
            dataType: 'json',
            success: function(response) {
                csrf_token = response.csrf_token;
                $('#csrf_token_2').val(csrf_token);

                // messages output
            },
            error: function() {
                // error message output
            }
        });
    });
});
</script>

Settings controller

public function update_settings()
{
    $csrf_token = $this->security->get_csrf_hash();

    $this->form_validation->set_rules('settings_one', 'Setting one', 'trim|required|xss_clean');
    $this->form_validation->set_rules('settings_two', 'Setting two', 'trim|required|xss_clean');
    $this->form_validation->set_rules('settings_three', 'Setting three', 'trim|required|xss_clean');

    if ($this->form_validation->run()) {
        if ($this->Settings_model->UpdateSettings($this->input->post('settings_one'), $this->input->post('settings_two'), $this->input->post('settings_three'))) {
            $data = array(
                'success' => true,
                'message' => 'Settings updated.',
                'csrf_token' => $csrf_token
            );
        } else {
            $data = array(
                'error' => true,
                'message' => 'Settings was not updated.',
                'csrf_token' => $csrf_token
            );
        }
    } else {
        $data = array(
            'error' => true,
            'settings_one_error' => form_error('settings_one'),
            'settings_two_error' => form_error('settings_two'),
            'settings_three_error' => form_error('settings_three'),
            'csrf_token' => $csrf_token
        );
    }

    echo json_encode($data);
}

public function upload_ico()
{
    $csrf_token = $this->security->get_csrf_hash();

    $favicon_upload_path = './upload/';

    if (isset($_FILES['favicon_image']['name'])) {
        $config['upload_path'] = $favicon_upload_path;
        $config['allowed_types'] = 'ico';

        $this->load->library('upload', $config);

        if (!$this->upload->do_upload('favicon_image')) {
            $data = array(
                'error' => true,
                'message' => $this->upload->display_errors(),
                'csrf_token' => $csrf_token
            );

        } else {
            $data = array(
                'success' => true,
                'message' => 'Favicon uploaded.',
                'csrf_token' => $csrf_token
            );
        }
    } else {
        $data = array(
            'error' => true,
            'message' => 'No file selected.',
            'csrf_token' => $csrf_token
        );
    }
    echo json_encode($data);
}

Config.php

$config['csrf_protection'] = TRUE;
$config['csrf_token_name'] = 'csrf_token';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array(
    'admin/settings'
);

I found issue.

I forgot to send token on first form, those 2 lines fix it

var token = $('#csrf_token_1').val();  // read token value from input
fd.append('csrf_token', token);

Then in script

<script>
$(document).ready(function() {
    var csrf_token = '';
    // upload favicon form
    $('#upload-favicon-form-btn').on('click', function(e) {
        e.preventDefault();

        var fd = new FormData();
        var files = $('#favicon_image')[0].files[0];
        fd.append('favicon_image', files);
        // fix for token that should be sent to controller over ajax
        var token = $('#csrf_token_1').val();  // read token value from input
        fd.append('csrf_token', token);        // append token value to data that need to be send to controller

and on each success in ajax it should update all forms id's with token returned from controller because i use $config['csrf_regenerate'] = TRUE; that is make new token on each request, so i made js function that updates token for all id's on all forms

// function that updates token on all forms
function updateToken(token) {
    $('#csrf_token_1, #csrf_token_2').val(token);
}

function in usage

success: function(response) {
    csrf_token = response.csrf_token;
    updateToken(csrf_token);
}

Maybe it's not best solution but works for me. If anyone have better one please feel free to post it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM