Welcome Guest, Not a member yet? Register   Sign In
CSRF regenerate with AJAX
#1

(This post was last modified: 04-14-2020, 03:42 PM by Leo.)

So, I've been fiddling with CSRF.
I found a way of keeping the regenerate setting on, with AJAX, like this:
PHP Code:
if ($this->request->isAJAX())
{
    $formDataRaw $this->request->getRawInput();
    $formDataRaw['csrf_token'] = csrf_hash();
    return $this->response->setJSON($formDataRaw);
} else {
    return '{ \'error\': \'Invalid Request\'}';

and then in ajax doing this
Code:
bla bla bla....ajax form code submit.done(function (data) {
                console.log(data);
                csrf.val(data.csrf_token)
            })
Where csrf is a variable for this: let csrf = $("input[name='csrf_token']",form);
Basically I retrieve a new generated value for the csrf cookie hash and replace the old value with the new one retreieved - and that works.
BUT I have a whole bunch of forms on my page (image editing)! And THEIR csrf values all stay the same so they don't work now.
Can someone suggest a way I can pass this value on and change the csrf input values on ALL my forms??

Also, having read the docs I'm still wondering is the auto CSRF protection in CI 4 turned on ONLY in app/config/Filters.php - because the docs say that when form_open() is used it's added automatically - but its not.

Also I didn't see much difference made when turning app.CSRFProtection  = true in the .env
You can see things I made with codeigniter here: itart.pro its not overly impressive as I have very little time to learn.
Reply
#2

(This post was last modified: 04-14-2020, 06:54 PM by Gary.)

I've also been fiddlig with the CSRF filter recently, and provided 'csrf' is uncommented in $globals, it will be inserted automatically in every form CI generates.  For example, on my test server, <?php echo form_open();?> generates this:
Code:
<form action="https://pig.pen/" method="post" accept-charset="utf-8">
<input type="hidden" name="csrf_token_name" value="4e01754b75479ae926ed73ea2a6aa86b">

If one generates the form manually, it won't be inserted automatically (or if the form's action="..." references another site).

Although it comes down to personal preferences, I generally don't bother with settings in the .env for anything (primarily because all of these settings seem to be available elsewhere too, and I loose track of what's set where).

Provided your page is regenerated EVERY time there's a submission, the csrf values on all the forms will automatically be regenerated.  I found the headache comes when the page isn't regenerated- and you need a new csrf value for each of the (many) possible ajax submissions links on the (same, unupdated) page.  Setting $CSRFRegenerate = FALSE; makes this headache go away... though many believe that this compromises security enough to warrent going the extra 1 mile.

I've put a few other security mechanisms in place to mitigate the risk, and may come back to it later.  I suspect one easy way (if there are many possible ajax submisions from a (static) page) would be to have all the PHP functions that accept ajax submissions return a new csrf token in their reply, which the ajax success routine would then store as a global, to use in the next submission it (or perhaps a differnt ajax submission routine) makes.
Reply
#3

(This post was last modified: 04-15-2020, 04:33 AM by Leo.)

I think I found a solution. Of course no page reloading. CSRF set to regenerate. A lot of forms on a single page. Please test, or point out if this may not work in some scenarios, if you have the time. Heres a simple, handy, dandy, vanilla javascript function that updates ALL present forms on the page with current csrf token:
Code:
function update_csrf_fields(value) {
    let all_forms = document.forms;
    for(e of all_forms) {
        e.querySelector('input[name=csrf_token]').value = value;
    }
}
Please note if your csrf_token is named something else - change the input name. Heres full example cycle:

Form submit using JQuery:
Code:
$(".form").submit(function(e) {
    e.preventDefault();
    let form = this;
    let choice = $(e.target).find("input[type=submit]:focus");
    if (choice) {
        if (choice[0].className === 'delete') {
            $.ajax({
                url: base_url + 'media/delete',
                type: 'POST',
                data: $(form).serializeArray(),
                dataType: 'json',
                headers: {'X-Requested-With': 'XMLHttpRequest'}
            }).done(function (data) {
                //show a message that I deleted the item, or do some fancy form.fadeOut() stuff
                update_csrf_fields(data.csrf_token); ////////TADA! This is where its AT!
            }).fail(function () {
                alert('Ajax Submit Failed ...');
            });
        }

        if (choice[0].className === 'save') {
            //a different submit button was pressed with save class
            //it will now do save actions with ajax
        }
    }
});
The controller ('media/delete'):

PHP Code:
public function delete()
{
        if ($this->request->isAJAX())
        {
            $formDataRaw $this->request->getRawInput();
            $formDataRaw['csrf_token'] = csrf_hash();
            return $this->response->setJSON($formDataRaw);
        } else {
            return '{ \'error\': \'Invalid Request\'}';
        }

You can see things I made with codeigniter here: itart.pro its not overly impressive as I have very little time to learn.
Reply
#4

Thx Leo! That's what I was looking for. Works great!
Reply
#5

(This post was last modified: 04-15-2020, 12:11 PM by Gary.)

That looks, and sounds like it's ok...

My only additional observaton is that if all the submissions are done using ajax (which may be the case, seeing: $(form).serializeArray() in your code), then sticking copies of the same token in every form is wasteful in terms of processing time and effort - having the token as an ajax global, or if you really want it stuck into the DOM, having a single copy of it inserted (somewhere close to the top of the document) that is then extracted (and appended to the next post) by the ajax submission call would seem more efficient.
Reply
#6

(This post was last modified: 04-15-2020, 12:32 PM by Leo.)

(04-15-2020, 12:09 PM)Gary Wrote: That looks, and sounds like it's ok...

My only additional observaton is that if all the submissions are done using ajax (which may be the case, seeing: $(form).serializeArray() in your code), then sticking copies of the same token in every form is wasteful in terms of processing time and effort - having the token as an ajax global, or if you really want it stuck into the DOM, having a single copy of it inserted (somewhere close to the top of the document) that is then extracted (and appended to the next post) by the ajax submission call would seem more efficient.
Naaah, it seems brutal, but its not wasteful with processing time - Javascript is client-side! Smile I think it can work with a hundred little forms no problem - but I never design sites with too many forms - this is for administration panel, editing and deleting photos. I tried to make it as plug-and-play as possible so I may use it in various sections of the admin panel.
You can see things I made with codeigniter here: itart.pro its not overly impressive as I have very little time to learn.
Reply
#7

Yeah!... screw those bastid users!
Reply
#8

(This post was last modified: 05-05-2020, 02:47 PM by Morgun_Andrey.)

(04-15-2020, 12:30 PM)Leo Wrote:
(04-15-2020, 12:09 PM)Naaah, it seems brutal, but its not wasteful with processing time - Javascript is client-side!  I think it can work with a hundred little forms no problem - but I never design sites with too many forms - this is for administration panel, editing and deleting photos. I tried to make it as plug-and-play as possible so I may use it in various sections of the admin panel.What is the problem? After you createvar csrf = $(\.csrf_token').val(); Wrote: <input type="hidden" class="csrf_token" name="csrf_token" value="<?= csrf_hash(); ?>">
            $.ajax({url: '<?= base_url() ?>/NameController/namefunction',
                    type: 'POST',
                    data: { "<?= csrf_token(); ?>": csrf},
                    dataType: "json"
                }).done(function (data) {
                $('.csrf_token').val(data.csrf);
NameController
 namefunction(){
return json_encode(array('result' => 1, 'csrf'=> csrf_hash()));
}
Reply
#9

(This post was last modified: 02-06-2023, 08:52 PM by 007basaran. Edit Reason: Additional Info )

Hello,

Maybe I didn't understand this question, but I wanted to talk about a problem I had before and its solution. When I tried to send an AJAX request using Codeigniter, I was getting 403 Forbidden Error because the csrf token was not renewed after the 1st request, then I produced the following solution, I got some code from you, you can use it if it works for you.

For using AJAX requests on Codeigniter 4, if you are using CSRF TOKEN for security, you just need refresh CSRF TOKEN for post replies.

You can add this function in your any helper class.

Code:
if (! function_exists('zs_refreshcsrf')) {
    function zs_refreshcsrf(){
        echo '
            <script>
                function update_csrf_fields() {
                    let all_forms = document.forms;
                    for(e of all_forms) {
                        var data = e.querySelector(\'input[name='.csrf_token().']\');
                        if(data) {
                            data.value = \''.csrf_hash().'\';
                        }
                    }
                }
               
                update_csrf_fields();
            </script>';
    }
}

Warning message function with bootstrap, i using this and you can use in any helper this code : 

Code:
if (! function_exists('zs_alertboost')) {
    function zs_alertboost($type, $msg){
        $allowed_types = array("primary","secondary","success","danger","warning","info","light","dark");

        if (in_array($type, $allowed_types)) {
            echo '<div class="alert alert-'.$type.'" role="alert"> '.$msg.' </div>';
        }else{
            echo '<div class="alert alert-warning" role="alert"> '.$msg.' </div>';
        }
    }
}

When users get any error from the controller, or the controller when returned with some data`s, if you not update csrf token you can get 403 Forbidden error.

You just need use this function in the controller before alerting to the user.

Code:
            if (! $this->validateData($post, $validation_rules)) {
                zs_refreshcsrf();
                return zs_alertboost("warning", validation_list_errors());
            }

Do not use token refresh code in your view page directly, because you can get error, you can use token refresh code only controller, because hash value can change every post request.
Reply
#10

everything is cool but this still suxs when you are working on more than 1 tab.
Learning Codeigniter 
Reply




Theme © iAndrew 2016 - Forum software by © MyBB