サマリ
- CakePHP 5.2 で「イベントリスナーからの値 return」が deprecated になりました
-
return $this->redirect(...)の deprecation を$event->setResult($this->redirect(...)); return;する形で修正したところ、 500 エラーdid not apply any authorization checksになってしまっていました -
parent::beforeFilter($event);していたところ、returnが伝播せず、結果として想定外に actionが走っていました。このとき、action内でauthorize()(認可)が実行され、redirect()の副作用でリダイレクトも成立していました -
setResult()にしたところ、parent::beforeFilter($event);していても、actionが呼ばれなくなります。結果cakephp/authorization利用時はactionが実行されず認可チェックも走らず、エラーとなっていました。 - 対処は、リダイレクトの手前で
$this->Authorization->skipAuthorization()を呼ぶこと。
修正後のコード:
public function beforeFilter(\Cake\Event\EventInterface $event): void
{
parent::beforeFilter($event);
if ($someCondition) {
// 短絡するとactionが実行されない=認可チェックも走らないので明示的にスキップ
$this->Authorization->skipAuthorization();
$event->setResult($this->redirect(['_name' => 'login']));
return;
}
}
きっかけ: CakePHP 5.2 の deprecation(イベントリスナーからの値 return)
beforeFilter(イベントリスナー)で、条件に応じて別ページへ飛ばすリダイレクトを書いていました。
public function beforeFilter(EventInterface $event)
{
// 中略
return $this->redirect(['_name' => 'login']);
}
ところが CakePHP 5.2 で「イベントリスナーから値を return する」のが deprecated になり、次の警告が出ます。
Since 5.2.0: Returning a value from event listeners is deprecated. Use
$event->setResult()instead
公式の推奨どおり $event->setResult() に直したところ—— 500エラー(認可エラー)が発生しました。
// 500 エラー(認可エラー)が発生
public function beforeFilter(EventInterface $event): void
{
// 中略
$event->setResult($this->redirect(['_name' => 'login']));
return;
}
なぜ修正前は動作していたのか
return $this->redirect(['_name' => 'login']); でもイベントは止まるはずなので、修正後のコードと同じ状況になると思いましたが、違いました。
別の原因で、結果的に action の実行はキャンセルされず、その中で認可を通る結果となっており、問題を抱えたまま動作していたという結論でした。
このプロジェクトでは、該当のフロー制御は親 AppController::beforeFilter() にて実装しており、各コントローラは下記のように記載していました(公式ドキュメントの通り)。
// 公式 5.x doc(Controllers)より
public function beforeFilter(EventInterface $event): void
{
parent::beforeFilter($event);
}
Remember to call
AppController's callbacks within child controller callbacks for best results
(日本語)最良の結果を得るために、子コントローラーのコールバック中で AppController のコールバックを呼ぶのを忘れないでください。
公式ドキュメントには上記のように記載されています。parent::beforeFilter($event); であって return parent::beforeFilter($event); ではありません。結果として、親メソッドの戻り値はどこにもリターンされない結果となります。
つまり、たとえ親コントローラーで AppController::beforeFilter() が return $this->redirect(...) で Response を返しても、
- 子の
parent::beforeFilter($event);(文)がリターン値を扱わず、
イベントリスナーとしての戻り値は null となります。そのため、action がそのまま実行されます。
では、なぜ修正前のコードは動いていたのかですが、
-
redirect()が$this->responseを 302(+ Location)に設定する。 -
return $this->redirect(...)で値を return しているにも関わらず、AppController::beforeFilter()側の実装のためactionの実行がキャンセルされず、その中で$this->Authorization->authorize(...)を実行していました。
1 によってリダイレクトは成立し、2 によって認可も満たされていました。「リダイレクトしているのにactionも走る」
意図せず action が実施されるので、アプリの不整合にもつながりえますし、セキュリティ的にも問題がある構成となっていました。
5.2 の deprecation 警告に従って setResult に直したことで、この「actionも走っていた」構成が発覚しました。
なぜ setResult だと 500 になるのか
setResult() は return で値を返すことをやめ、$event オブジェクトを書き換えます。そのため parent::beforeFilter($event); などによらず、イベントの結果は確実に伝播します。
結果として、actionの実行がキャンセルされます。
公式ドキュメント Controllers — Using Redirects in Controller Events の掲載コード:
// 公式 doc より
public function beforeFilter(EventInterface $event): void
{
if ($this->request->getParam('prefix') !== 'Admin') {
$event->setResult($this->redirect('/'));
return;
}
}
なお、この
setResultを使うリダイレクトの節(Using Redirects in Controller Events)は英語版の Controllers ドキュメントにのみ記載されており、日本語版には対応する記載がありませんでした(2026/6時点)。
actionが実行されないため、その中で呼んでいた $this->Authorization->authorize(...) も呼ばれません。すると cakephp/authorization 利用時に次の例外が出ます。
Authorization\Exception\AuthorizationRequiredException:
The request to `/dashboard` did not apply any authorization checks.
AuthorizationMiddleware は、リクエスト処理後に「認可チェックが一度でも行われたか」を確認し、行われていなければ例外を投げます。
# Middleware/AuthorizationMiddleware.php より抜粋
if ($this->getConfig('requireAuthorizationCheck') && !$service->authorizationChecked()) {
throw new AuthorizationRequiredException(['url' => $request->getRequestTarget()]);
}
このため AuthorizationRequiredException となり、500 エラーとなります。
これは setResult 固有の問題ではなく、「リダイレクトでactionが実行されない、そのため、認可チェックが行われない」という問題です。
同じ問題は、FriendsOfCake/Search の PRG パターンでも報告されていました(cakephp/authorization #128)。
対処方法: redirect 前に skipAuthorization を呼ぶ
上記公式 issue で示されているように、beforeFilter で $this->Authorization->skipAuthorization() を呼ぶことで解決します。
public function beforeFilter(EventInterface $event): void
{
if ($this->request->getParam('prefix') !== 'Admin') {
// この分岐は action が実行されない=認可チェックも行われないため、明示的にチェックをスキップする
$this->Authorization->skipAuthorization();
$event->setResult($this->redirect('/'));
return;
}
}
beforeFilter でリダイレクトする場合、リダイレクト先で改めて beforeFilter が走り、そこで適切に認可チェックされます。したがって、安全に skipAuthorization することができます。
注:
skipAuthorization()はbeforeFilterで呼ぶ必要があります。actionの先頭に置いても、そのaction自体が実行されないので効きません(先の issue #128 での指摘)。
なぜ今まで気づかなかったのか
修正前のコード状況になってしまったのは何故なのか、気になって調べてみました。
CakePHP の変遷をたどると分かったことがありますのでまとめます。
| CakePHP |
redirect() の挙動 |
beforeFilter で短絡する方法 | 子クラスからの parent::beforeFilter()
|
|---|---|---|---|
| 2.x |
$exit=true で実行停止
|
$this->redirect() を呼べば即停止 |
影響なし(その場で止まる) |
| 3.x / 4.x | Response を返すだけ | 実質 return $this->redirect()
|
子が親クラスの beforeFilter() を呼ぶと、return された値が処理されず、止まらず action が実行されてしまう |
| 5.x | 同上 |
$event->setResult($this->redirect())(5.x で新設) |
影響なし(setResult がイベントの結果を書き換えるので止まる) |
-
2.x は
redirect()がスクリプトを停止していた(2.x API: "Script execution is halted after the redirect.")ので、問題ありませんでした。 -
3.x→4.x で
redirect()が停止しなくなり、「return で値を返して止めること」が必要になりましたが、parent::beforeFilter($event);を使うと return された値が処理されず、止まらなくなり、意図せぬ action の実行を招きます。 -
5.x で
setResultで値を渡すようになり、returnでの値渡しは deprecated になりました。setResultなら、parent::beforeFilter($event);でも問題なく停止します。
つまり今回の 500 は「setResult にしたことで、action の実行がキャンセルされたため、これまで隠れていた問題が顔を出した」ということでした。