質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.49%

CodeIgniterのCSRFプロテクションを有効化した状態でajaxを使用する

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 3,767

score 6

現在、ドットインストールの『PHPでTodo管理アプリを作ろう』を参考に、CodeIgniter3で簡単なTodoアプリを作成中です。
config.phpで$config['csrf_protectin] = TRUEおよび$config['csrf_regenerate'] = TRUEとしたうえで、以下のようなコードをviewに書きました(一部抜粋)。

<scritp>
    $(function() {
        // チェックボックスをクリックして、Todoの完了/未完了を切り替える(取り消し線の表示/非表示を切り替える)
        $('#todos').on('click', '.update_todo', function() {
            var id = $(this).parents('li').data('id');
            $.ajax({
                type: 'POST',
                url: '<?php echo site_url('todos/update'); ?>',
                data: {
                    '<?php $this->security->get_csrf_token_name(); ?>': '<?php $this->security->get_csrf_hash(); ?>',
                     id: id
                },
                dataType: 'json'
            })
            .done(function(result) {
                if (result === '1') {
                    $('#todo_' + id).find('.todo_title').addClass('done');
                } else {
                    $('#todo_' + id).find().removeClass('done');
                }
            });
       });


上記に加えて、追加および削除の機能も実装済みです。
なお、Todoを追加するための入力フォームにはform_open()を使用しています。

CSRFプロテクションが無効であれば、すべての機能が正常に動作します。
しかし、CSRFプロテクションを有効化すると、ページ読み込み後の最初の1回しかajaxが動いてくれません。

ちなみに$config['csrf_regenerate']をFALSEに設定してみたところ、CSRFプロテクションが無効であるときと同様に、そのような問題は発生しませんでした。

あくまで$config['csrf_protectin] = TRUEおよび$config['csrf_regenerate'] = TRUEとしたうえで、ajaxを利用したいのですが、どなたか解決策をお教えいただけないでしょうか。

どうぞよろしくお願いいたします。

 追記

ここを参考に(というか丸写しして)、次のように書きました。

<script>
    csrf_token_name = '<?php echo $this->security->get_csrf_token_name(); ?>'; // 追加
    csrf_cookie_name = '<?php echo $this->config->item('csrf_cookie_name'); ?>'; // 追加
    $(function() {
        'use strict';

        // 追加 --------------------------------------------------------
        var object = {};
        object[csrf_token_name] = $.cookie(csrf_cookie_name);
        $.ajaxSetup({
            data: object
        });
        $(document).ajaxComplete(function() {
            object[csrf_token_name] = $.cookie(csrf_cookie_name);
            $.ajaxSetup({
                data: object
            });
        });
        // -------------------------------------------------------------

        // update
        $('#todos').on('click', '.update_todo', function() {
            var id = $(this).parents('li').data('id');
            $.ajax({
                type: 'POST',
                url: '<?php echo site_url('todos/update'); ?>',
                data: {
                    id: id
                },
                dataType: 'json'
            })
            .done(function(result) {
                if (result === '1') {
                    $('#todo_' + id).find('.todo_title').addClass('done');
                } else {
                    $('#todo_' + id).find('.todo_title').removeClass('done');
                }
            });
        });

        // create
        $('#new_todo_form').on('submit', function() {
            var title = $('#new_todo').val();
            $.ajax({
                type: 'POST',
                url: '<?php echo site_url('todos/create'); ?>',
                data: {
                    title: title
                },
                dataType: 'json'
            })
            .done(function(result) {
                var $li = $('#todo_template').clone();
                $li
                    .attr('id', 'todo_' + result.id)
                    .data('id', result.id)
                    .find('.todo_title').text(title);
                $('#todos').prepend($li.fadeIn());
                $('#new_todo').val('').focus();
            });
        })

        // delete
        $('#todos').on('click', '.delete_todo', function() {
            var id = $(this).parents('li').data('id');
            if (confirm('are you sure?')) {
                $.ajax({
                    type: 'POST',
                    url: '<?php echo site_url('todos/delete'); ?>',
                    data: {
                        id: id
                    }
                })
                .done(function() {
                    $('#todo_' + id).fadeOut(400);
                });
            }
        });
    });
</script>


こうすると、$config['csrf_regenerate'] = TRUEの状態でもupdateとdeleteのajaxが正常に動きます。
ただ、createを実行すると、"The action you have requested is not allowed."のエラーページに飛ばされてしまいます(Todoの作成自体はできているようです)。

原因をお教えいただければ幸いです。
どうぞよろしくお願いいたします。

 追記

上記のコードは、createのところでfalseを返し忘れています。
試してはいませんが、done()メソッドの後にreturn false;を挿入することで、すべてのajaxが正常に動作するかもしれません。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

あくまで$config['csrf_protectin] = TRUEおよび$config['csrf_regenerate'] = TRUEとしたうえで、ajaxを利用したいのですが、どなたか解決策をお教えいただけないでしょうか。

$config['csrf_regenerate'] = TRUE としたい理由は何でしょうか?


追記

今回のようなアプリケーションの場合、regenerateを切っておくのが普通というか、有効にする理由がないのでしょうか。

例えば、以下のドキュメントには次のように書かれています。

https://codeigniter.jp/user_guide/3/libraries/security.html

トークンは、すべてのサブミット時に再生成する(デフォルト)か、 CSRF クッキーの生存期間は同一の値で維持するかのどちらかになります。 デフォルトであるトークン再生成はより厳格なセキュリティを提供しますが、 他のトークンが無効になることでユーザビリティの問題をもたらす可能性があります (戻る/進むナビゲーション、複数のタブ/ウィンドウ、非同期アクションなど)。 次の config パラメータを編集することによって、この動作を変更することができます。

今回のケースは非同期アクションに該当しますね。つまり、元々$config['csrf_regenerate'] = TRUE が使えないシーンであるわけです。
それでは、$config['csrf_regenerate'] = TRUE でないとCSRF攻撃にあう可能性が高まるかというと、そんなことはありません。なので、$config['csrf_regenerate'] = TRUE とするために、トリッキーなことをしてしまうと、かえって、その複雑でトリッキーな処理に脆弱性が入る可能性が高くなります。
アプリケーションは単純に書くほどバグは少なくなるわけで、脆弱性もバグの一種ですから、複雑なことをすると脆弱性も傾向としては増えるのです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/02 15:59

    ockeghemさん、コメントをありがとうございます。

    > $config['csrf_regenerate'] = TRUE としたい理由は何でしょうか?

    あくまで、練習用に作っているので、「$config['csrf_regenerate'] = TRUE」としなければならない現実的な問題はございません。
    ただ、もしできるのであれば、その方法を学んでおきたいと思いました。

    今回のようなアプリケーションの場合、regenerateを切っておくのが普通というか、有効にする理由がないのでしょうか。

    キャンセル

  • 2018/09/03 01:47

    ockeghemさん、詳細なご回答をありがとうございます。

    引用元のページは開いたことがあるはずですが、よく読めていなかったようです。
    $config['csrf_regenerate'] = TRUE が使えない/使うべきでないパターンであるとのこと、承知いたしました。

    > アプリケーションは単純に書くほどバグは少なくなるわけで、脆弱性もバグの一種ですから、複雑なことをすると脆弱性も傾向としては増えるのです。

    こちらを肝に銘じておきます。
    このたびはありがとうございました。

    キャンセル

+2

$config['csrf_regenerate'] = TRUEとするのであれば、ajax 通信させるごとに ajax 内の token を更新することが必要かと。
ただ、タイミングとか順番とか考えだすと、現実的な実装ではない気がします。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/02 15:46

    te2jiさん、ご回答をありがとうございます。
    質問に追記しましたので、もしよろしければご確認いただけないでしょうか。

    キャンセル

  • 2018/09/02 16:07

    コードレビューするつもりはないので追記のコードは読んでいませんが、多分回答に書いたことをコードに落としてあるんだと思います。変数の変化を追って見ると良いです。

    キャンセル

  • 2018/09/02 16:18

    承知いたしました。ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.49%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る