Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Many AJAX requests at once with CSRF protection

Hi everybody.

My web application is based on asynchronous requests. Timer widget is working and updating it's status every second by AJAX (yes, it is necessary).

I am sending with each AJAX my CSRF tokens:

project_data.append(csrf_name_key,csrf_name_value);
project_data.append(csrf_value_key,csrf_value_value);

And in response I am updating that global variables:

function setCSRF(response) {
    csrf_name_key = response.nameKey;
    csrf_name_value = response.name;
    csrf_value_key = response.valueKey;
    csrf_value_value = response.value;      
}

Everything is generally fine. But if I will do another AJAX for example when I change task in todo list to "done" it sometimes ending with error because I am sending AJAX before I am getting new tokens from previous request.

I really don't know how to do solve that problem. First idea was that I will make "like stack array" with 5 different tokens but one https request = one pair of tokens and I can't generate it.

Maybe some type of queue of ajax requests, but what with doing them in a right time - I don't know.

My actual pseudo-solution is "if failed try again max 10 times":

if(e.target.response=="Failed CSRF check!") {
    if(failedAjax<10) checkForSurvey();
    failedAjax++;
    return;
}

It is generally working, but errors are appears in a console and it is very dirty solution.

I am using Slim 3 microframework with CSRF extension. Really please for help with that interesting problem.

I will be very thankful,

Arthur

like image 486
Artimal Avatar asked Oct 18 '22 14:10

Artimal


1 Answers

There are some options for you:

  1. Use a stack of csrf-tokens inside you javascript code

  2. Use a csrf token which is can be used more than once (not so secure)

  3. Use a queue for the request

A stack for the tokens

The Slim-Csrf-middleware provides functionallity for you, to generate these tokens, you just need to get them to the clientside. You could do an api for getting 5 csrf tokens, this api would also consume on csrf-token.

Add an api and generate the tokens there.

$app->get('/foo', function ($request, $response, $args) {
    // check valid  csrf token

    $tokens = [];
    for ($i = 0; $i < 5; $i++) {
        $tokens[] = $this->csrf->generateToken();
    }

    return $response->withJson($tokens);
});

Now the csrf-token are valid through the whole user session.

Guard::generateToken() returns something like this:

array (size=2)
  'csrf_name' => string 'csrf58e669ff70da0' (length=17)
  'csrf_value' => string '52ac7689d3c6ea5d01889d711018f058' (length=32)

A multi-use csrf-token

For that, Slim-Csrf already provides functionallity with the token persistance mode. That can be enabled through the constructor or the Guard::setPersistentTokenMode(bool) method. In my example, I'm doing this with the method:

$container['csrf'] = function ($c) {
    $guard = new \Slim\Csrf\Guard;
    $guard->setPersistentTokenMode(true);
    return $guard;
};

Here the PhpDoc from the persistanceTokenMode-attribute

/**
 * Determines whether or not we should persist the token throughout the duration of the user's session.
 *
 * For security, Slim-Csrf will *always* reset the token if there is a validation error.
 * @var bool True to use the same token throughout the session (unless there is a validation error),
 * false to get a new token with each request.
 */

A queue for the ajax requests.

Add a queue for the request, that could be delay the execution of your request but there will always be a valid csrf token.

This should be seen as pseudocode as I havn't tested this yet.

var requestQueue = [];
var isInRequest = false;

var csrfKey = ''; // should be set on page load, to have a valid token at the start
var csrfValue = '';

function newRequest(onSuccessCallback, data) { // add all parameters you need
    // add request to the queue
    requestQueue.push(function() {
        isInRequest = true;
        // add to csrf stuff to data
        $.ajax({
            data: xxx
            url: "serverscript.xxx",
            success: function(data) {
                // update csrfKey & csrfValue
                isInRequest = false;
                tryExecuteNextRequest(); // try execute next request
                onSuccessCallback(data); // proceed received data
            }
        }});
    );
    tryExecuteNextRequest();
}

function tryExecuteNextRequest() {
    if(!isInRequest && requestQueue.length != 0) { // currently no request running &
        var nextRequest = requestQueue.shift();
        nextRequest(); // execute next request
    }
}
like image 132
jmattheis Avatar answered Oct 20 '22 22:10

jmattheis