こんにちは。inglowの開発担当です。
今回は、前回に引き続き、javascriptのライブラリ「Angular.js」とcakePHP3.xを利用した場合の非同期通信の処理を生成する第二回目です。第一回目の記事はこちらです。
Angular.js×cakePHP3.xで非同期通信の実装 その1
今回使った技術は下記です。
- PHP(cakePHP3.8)
- javascript(angular.js)
目次
Angular.js×cakePHP3.x
前回は、フォームのsubmit時に「ng-submit」で指定した処理が動くようにしました。
しかし、この処理で非同期通信でデータを送信すると、「CSRFトークンが一致していない」というエラーが返ってくるかと思います。
今回は、このcakePHP3.xの「CSRF対策」をどう共存させていくかという点を書いていきたいと思います。
cakePHP3のCSRF対策
cakePHP3.xでは、CSRF対策は「ミドルウェア」と呼ばれるもので動くようになっています。
このミドルウェアは、routes.phpでスコープ毎(プレフィックスや特定のcontrollerの指定)に読み込むかどうかを指定します。
cakePHPのプロジェクトを生成したばかりのデフォルトの場合はアプリケーション内すべてのURLでCSRF対策が動くようになっています。
今回は、
の2通りのやり方を試してみました。
方法その1 CSRFミドルウェアの設定を変更する
アプリケーション全体でCSRF対策をしない場合は、routes.phpのミドルウェア読み込み部分をコメントアウトするだけでOKです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Router::scope('/', function (RouteBuilder $routes) { // Register scoped middleware for in scopes. //ここの3行をコメントアウトする //$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware([ // 'httpOnly' => true //])); /** * Apply a middleware to the current route scope. * Requires middleware to be registered via `Application::routes()` with `registerMiddleware()` */ //ここの1行をコメントアウトする //$routes->applyMiddleware('csrf'); ・ ・ ・ $routes->fallbacks(DashedRoute::class); }); |
しかし、ブラウザで表示するページまでCSRFチェックされない状態が良くない場合もあります。その場合は、prefixを分けて設定をします。
まず、routes.phpで非同期通信用のプレフィックスを用意します。
今回は[app/api/…..]というURLになるように変更していきます。
1 2 3 4 5 6 7 8 9 |
/* 最初に出てくる * 「Router::scope('/', function(RouteBuilder $routes){」 * の外に記述する */ Router::prefix('api', function (RouteBuilder $routes) { Router::scope('/', function(RouteBuilder $routes){ }); $routes->fallbacks(DashedRoute::class); }); |
ミドルウェアの記載をしなければ、これで「app/api/~」のURLの処理はCSRFチェックなしで非同期通信ができます。
あとは、src/Controller/Api/の階層を作成し、Controllerを作成していきます。
この方法であれば、通常の画面にはcakePHPのCSRFチェックを使い、APIの通信の場合は使用しない状態にできます。ただし、外部のどんなURLからでもPOSTできる状態になってしまうので、リファラーでドメイン名をチェックしたり、自分でトークンチェックの仕組みをする必要があります。
方法その2 CSRF対策のトークンを一緒に送信する
「jquery cakePHP ajax」で調べるとよく見つかる方法です。anguler.jsの場合はどうなるのか試してみました。
まずは、通信の前にcsrfトークンを取得する処理を書いていきます。
1 |
var csrf_form = document.querySelectorAll('#' + $scope.form_name + ' input[name="_csrfToken"]'); |
次に、取得したCSRFトークンを通信する際のヘッダーに設定します。
1 |
$http.defaults.headers.common['X-CSRF-Token'] = csrf_form[0].value; |
最後に通信処理を記載したらOKです。
全容はこのような感じになります
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var recipCtl = function($scope, $http){ $scope.form_name = 'recip_edit'; //送信するフォームを指定できるよう、Angular.js側でフォーム名を定義 $scope.actnm = null; $scope.sbmt = function(){ //csrfのトークンを取得する var csrf_form = document.querySelectorAll('#' + $scope.form_name + ' input[name="_csrfToken"]'); $http.defaults.headers.common['X-CSRF-Token'] = csrf_form[0].value; //非同期通信処理を行う $http({ 'method':'POST', 'url':'~~~/api/items/actionname', 'data':{ /* 送信するデータをここに */ }, }).success(function(data){ //通信成功時の処理 }).error(function(data){ //通信失敗時の処理 }); } }; |
この方法の場合は、トークンチェックをcakePHPに任せているので自分で特別処理を書く必要はありませんが、一度FormヘルパーでForm->create()をする必要があります。
さいごに
どちらの方法も一長一短という感じです。
例えば、外部のアプリケーションからも通信できるようにするのであれば、「方法1」、モーダルウィンドウを出して入力内容を通信したいという場合であれば「方法2」という感じになるかなと思います。
非同期通信でのCSRFチェックの共存は、Angular.jsを利用する場合にcakePHPを利用するにあたって避けては通れない部分だったので、まとめてみました。