CakePHP4でサービス間連携APIのCSRFチェックを無効にする
関連記事でセキュリティについて考えていて、こちらもセキュリティの話なのですがCSRFについても考えてみました。CSRFチェックはWebブラウザからPOSTとかされるときに送り元が安全であるかを確認するものですが、サービス間でAPIを呼ぶ場合にこの安全をチェックするためのCSRFトークンが存在しないのでCSRFチェックしようがないなというところと、その代わりにAuthorizationを使ったアクセストークンを使っているというところから、まぁこの通信に関してはCSRFチェックを無効にしておいて問題ないだろうということで無効にする手段を考えることにします。
CSRFチェックの制御はどこでしているか確認する
公式ドキュメントで確認するとApplication.phpのmiddlewareメソッド内でCsrfProtectionMiddlewareに設定を入れることで制御するみたいです。CakePHP2だと各コントローラーで制御できたのですが、4だと全体のミドルウェアとして制御するようですね。ここで特定コントローラーの特定アクションはサービス間連携用のAPIなので無視とかっていう設定を入れることはできますけど、そんなことをしだすとAPIが増えるたびに設定の編集が必要になるし管理も大変になるので、できればルールとしてこの塊はCSRFチェックは無効という形にしたいです。たしかルーティングの設定でこのフォルダ以下みたいに設定できたような気がするのでちょっと調べてみたらプレフィックスっていうのでフォルダを切り分けられるっぽいかったです。
サービス間連携用APIはapiフォルダ以下にまとめることにする
apiフォルダに連携用APIをまとめておいて、CsrfProtectionMiddlewareでは”api”プレフィックスのものはCSRFチェック無効っていう設定をすれば具合が良さげ。関連記事の方で作ったプログラムに手を入れていこうと思います。routes.phpを開くとそれっぽいコメントがあったので、その下くらいに設定を書いていこうと思います。
/*
* If you need a different set of middleware or none at all,
* open new scope and define routes there.
*
* ```
* $routes->scope('/api', function (RouteBuilder $builder): void {
* // No $builder->applyMiddleware() here.
*
* // Parse specified extensions from URLs
* // $builder->setExtensions(['json', 'xml']);
*
* // Connect API actions here.
* });
* ```
*/
$routes->scope('/api', function (RouteBuilder $builder): void {
$builder->connect('/{controller}/{action}/*', ['prefix' => 'api']);
});“src/Controller”に”Api”フォルダを作成し、ControllerフォルダにあるTestController.phpをApiフォルダ直下にコピーします。namespaceだけ修正します。(このあたりは試しながらたどり着いた感じです)
<?php
namespace App\Controller\Api;
use Cake\Http\Client;
use Cake\Log\Log;
use App\Controller\AppController;
class TestController extends AppController
{
public function initialize(): void
{
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function index()
{
$this->set('Authorization', $this->request->getHeaderLine('Authorization'));
$this->viewBuilder()
->setClassName('Json')
->setOption('serialize', ['Authorization'])
->setOption('jsonOptions', JSON_FORCE_OBJECT)
;
}
}そしてCSRFチェックの対象からapiプレフィックスを外します。今回はテストなのでhttpOnlyはコメントアウトしています。
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$csrf = new CsrfProtectionMiddleware(/*['httpOnly' => true]*/);
$csrf->skipCheckCallback(function ($request) {
return $request->getParam('prefix') === 'api';
});
(省略)
$middlewareQueue
(省略)
// ->add(new CsrfProtectionMiddleware([
// 'httponly' => true,
// ]));
->add($csrf);
ではPostmanでController直下のTestControllerとApiフォルダのTestControllerそれぞれに対してPOSTでアクセスしてみます。


というわけでURLにapiを含む方はCSRFチェックを回避できました。
まとめ
サービス間連携用APIでCSRFチェックをしない方法が分かりました。
