AjaxとCsrfトークンの取り扱いについて【CakePHP4】

CakePHPでAjaxを利用する際には、csrfトークンについての知識が必要になります。

今回の記事では、具体的な使い方について紹介します。

CakePHPでのAjaxの使い方

Templates/samples/demo.php
(テンプレートのどこかに下記のコードを挿入)

<input type="hidden" name="_csrfToken" autocomplete="off" value="<?= $this->request->getAttribute('csrfToken') ?>">

sample.js

var submitData = {};
submitData.hoge = 1;
submitData.piyo = 2;

$.ajax({
      type: 'post',
      url: '/samples/demo',
      headers: { 'X-XSRF-TOKEN': $('input[name="_csrfToken"]').val() },
      beforeSend: function (xhr) {
        xhr.setRequestHeader('X-CSRF-Token', $('input[name="_csrfToken"]').val());
      },
      dataType: 'json',
      data: {
        "_csrfToken": $('input[name="_csrfToken"]').val(),
        "data": submitData
      },
      async: true,
      cache: false,
}).then(function (data) {
      console.log(data);
});

このようにしないと、ajaxで送信した際に以下のようなエラーが出ます。

Missing CSRF token body

CakePHPではデフォルトでCSRFによるサイト攻撃に対する対策がなされており、普通にAjaxを組み込むだけではうまく動きません。

CSRFとは何か?については下記外部サイト様をご参照ください。

CSRF(クロスサイトリクエストフォージェリ)とは?被害と対策も
CSRFとはクロスサイトリクエストフォージェリの略であり、Webアプリケーションの脆弱性を利用したサイバー攻撃の一種です。CSRFとはどんなサイバー攻撃なのか。また、CSRFによって起こり得る被害や対策についてご説明します。

外部サイトからAjaxでアクセスしたい場合

CakePHPで作ったウェブサービスに、外部サイトからAjaxでアクセスしたい場合があります。

このような場合には、当然CSRFトークンが仕込まれていないので、普通にAjaxでアクセスするとエラーが発生してしまいます。

外部サイトからのAjaxアクセスを受け入れたいページだけ、CSRF対策を停止する必要があります。

コードの編集箇所と解説

src/Application.phpの関数middleware()を編集します。

●コード全体

//src/Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
    {
    //追記する箇所①
        $csrf = new CsrfProtectionMiddleware(['httponly'=>true]);

        $csrf->skipCheckCallback(function ($request) {
            $controller = $request->getParam('controller');
            $action = $request->getParam('action');

            //https://ドメイン/hoge/piyoで表示するページはcsrf対策を無効にする
            if ($controller=="piyo || "$action == "hoge") {
                return true;
            }
            return false;
        });

        $middlewareQueue
        // Catch any exceptions in the lower layers,
        // and make an error page/response
        ->add(new ErrorHandlerMiddleware(Configure::read('Error')))

        // Handle plugin/theme assets like CakePHP normally does.
        ->add(new AssetMiddleware([
            'cacheTime' => Configure::read('Asset.cacheTime'),
        ]))

        // Add routing middleware.
        // If you have a large number of routes connected, turning on routes
        // caching in production could improve performance. For that when
        // creating the middleware instance specify the cache config name by
        // using it's second constructor argument:
        // `new RoutingMiddleware($this, '_cake_routes_')`
        ->add(new RoutingMiddleware($this))

        // Parse various types of encoded request bodies so that they are
        // available as array through $request->getData()
        // https://book.cakephp.org/4/en/controllers/middleware.html#body-parser-middleware
        ->add(new BodyParserMiddleware())

        // Cross Site Request Forgery (CSRF) Protection Middleware
        // https://book.cakephp.org/4/en/security/csrf.html#cross-site-request-forgery-csrf-middleware

        //ここをコメントアウトする
        //->add(new CsrfProtectionMiddleware([
        //    'httponly' => true,
        //]));

        //追記する箇所②
        ->add($csrf);

        return $middlewareQueue;
}

●追記する箇所①:特定のコントローラ、アクションのみcsrfチェックをスキップする

$csrf = new CsrfProtectionMiddleware(['httponly'=>true]);

$csrf->skipCheckCallback(function ($request) {
       $controller = $request->getParam('controller');
       $action = $request->getParam('action');

       //https://ドメイン/hoge/piyoで表示するページはcsrf対策を無効にする
       if ($controller=="piyo || "$action == "hoge") {
            return true;
       }
       return false;
});

●追記する箇所②:①で追記した設定を反映する

//コメントアウトする
//->add(new CsrfProtectionMiddleware([
//    'httponly' => true,
//]));

//追記する箇所②
->add($csrf);

これで特定のページのみCSRF対策を無効にできます。

サイト全体でCSRFチェックを外す方法もありますが、セキュリティー的に危険なので、部分的に外すようにしましょう。

参考

・CakePHP4 CookBook : ミドルウェアCSRF

コメント

タイトルとURLをコピーしました