CodeIgniter3でajaxを行う際に403が返ってくる時の対処法2つ

CodeIgniter3でajaxを使っていると、適切なコードを書いているはずなのに403が返ってきた。

これを機に、CodeIgniterのajax周りについて調べたのでメモしておく。

開発環境

  • CodeIgniter 3.1
  • PHP 7.4.1
  • Mac 10.14

CodeIgniterのajaxで403が返る原因

CodeIgniterでajaxを行う際に403が返る原因として、CodeIgniterのcsrf対策の設定が考えられる。(「クロスサイトリクエストフォージェリ - Wikipedia」)

CodeIgniterには、configs/config.php内に以下のようなコードがある。

$config['csrf_protection'] = TRUE;
$config['csrf_token_name'] = 'csrf_test_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array();

CodeIgniterのcsrg対策は、上記のコードの$config['csrf_protection'] = TRUE;TRUEにすることで自動で行うようになっている。

あとは、Controller内で$this->load->helper('html')としてhtmlヘルパーを呼び出し、viewで<?php echo form_open('post/create'); ?>等とフォームを呼び出すことで、自動でcsrfトークンを追加してくれる。

しかし、問題は$config['csrf_regenerate'] = TRUE;の部分。デフォルトではcsrfトークンは1回限りしか使えないようになっており、ajaxで何回も通信を送ることができないようになっている。

ajaxで403が返ってくる対策法2つ

以上のことを踏まえて、ajaxで403が返ってきた時の対処法は以下の2つが考えられる。

$config['csrf_regenerate']をFALSEにする

一番簡単な方法は、$config['csrf_regenerate'] = FALSEのすることだ。これだけでcsrfトークンが毎回再発行されなくなり、ajaxで同じトークンを使い回せる。

セキュリティ的には少し甘くなるのが欠点だが、最も簡単な対策と言える。

ajaxのたびにトークンを再発行。

2つ目の方法は、ajaxでresponseを送るたびにトークンを再発行する方法。

CodeIgniterには$this->security->get_csrf_hash()と言う再発行したcsrfトークンのハッシュ値を返すメソッドが用意されており、これを使えばうまく処理ができる。

参考:セキュリティクラス — CodeIgniter 3.2.0-dev ドキュメント

具体的には、Controller内では以下のように処理を書く。

<?php
$results = [
    'content' => $content,
    'csrf_token' => $this->security->get_csrf_hash(),
];

$this->output
    ->set_content_type('application/json')
    ->set_output(json_encode($results));


そしてajaxでresponseを受信した時に、formのcsrfトークンの値を変更すればOK.

xhr.onreadystatechange = () => {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            let response = JSON.parse(xhr.responseText);
            let csrf_input = document.getElementsByName("csrf_test_name")[0];
            content.innerHTML = response.content;
            csrf_input.value = response.csrf_token;
        }
    }
}

もっと良い方法があるよ、という場合はコメントください。