XMLHttpRequest запрос на Yii2 с использованием CSRF токена

XMLHttpRequest запрос на Yii2 с использованием CSRF токена

· JavaScript и Снипеты · 3 мин чтения

В этой записи пойдет речь о том, как сделать безопасный и правильный XMLHttpRequest в Yii2 используя CSRF токен. 

Если вы не знаете, что такое CSRF и что за ним стоит, то ниже вы можете прочитать краткое описание.

CSRF (Cross-Site Request Forgery) — это атака, с помощью которой хакер может выполнить массу различных действий от имени других, зарегистрированных посетителей.

behaviour() и XMLHttpRequest

Давайте представим, что у вас есть контроллер с action, который принимает только POST запрос (в Yii это делается с помощью behaviour() метода).

Пример behaviour():

public function behaviors() {
    return [
        ...
        'verbs' => [
            'class' => VerbFilter::className(),
            'actions' => [
                'news' => ['post']
            ],
        ],
    ];
}

В этом примере, я указываю, что action news принимает только POST запросы и если пользователь попытается отправить какой-либо другой, ему выдаст ошибку.

В случае с POST запросом, вам так же нужно отправить CSRF токен, который автоматически генерируется в Yii2. Об это я распишу далее в этом посте.

После того как у нас готов action и настроили какие запросы могут на него приходит, самое время сделать XMLHttpRequest с CSRF токеном.

Для примера, я написал callback функцию, которая работает на подобии Ajax в jQuery. Суть ее в том, чтобы она отправляет POST запрос по ссылке. Для корректной работы функции, нужно указать 3 аргумента.

  • Первый — это токен
  • Второй — это объект, внутри которого есть параметр url (у вас может быть что-то другое)
  • Третий — это callback
function sendPostRequest(_csrf, news, callback) {  
    var xmlhttp = new XMLHttpRequest();
    
    var _postdata = 'title=' + news.title + '&_csrf=' + this.options._csrf;

    self = this;

    xmlhttp.open("POST", 'http://domain.ru/api/news', true);

    xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    xmlhttp.setRequestHeader('X-CSRF-Token', _csrf);
    xmlhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

    xmlhttp.onload = function() {
        if(xmlhttp.readyState == XMLHttpRequest.DONE && xmlhttp.status == 200) {
            callback(xmlhttp.responseText);
        } else if (xmlhttp.status == 400) {
            throw Error('Ошибка: ' + xmlhttp.status);
        } else {
           throw Error('Ошибка, что-то пошло не так.');
        }
    }

    xmlhttp.send(encodeURI(_postdata));
}

В _postdata нужно вписать параметры, которые будут отправляться в action. В данном примере — это title и _csrf. Они могут быть приняты как Yii::$app->request->post('title') или Yii::$app->request->post('_csrf'), но токен нам точно не понадобиться, потому что он проверяется в Yii2 автоматически и если он отличается, то выдаст ошибку в доступе.

Так же не забудьте указать следующий header:

  • X-CSRF-Token (сам токен)

Потому что без него, у вас так же будет выводить ошибку в доступе.

А теперь давайте покажу как правильно вызвать эту callback функцию (чтобы дождаться ответа и продолжить работу скрипта):

sendPostRequest('<?=Yii::$app->request->csrfToken?>', 'Заголовок новости', function(data) {
  console.log('Ответ получен:');
  console.log(data);
});

Вызываем ее как callback функцию и как только получает ответ от action, он сразу же возвращается обратно к нам. В data будет находится ответ из экшена.

Если вам нужно получить CSRF токен с помощью JavaScript, вы можете использовать метод yii.getCsrfToken().

 

Источники

  • https://yii2-cookbook.readthedocs.io/csrf/
  • https://stackoverflow.com/a/27248827/3661795
  • https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest