#やりたいこと
Laravelでは「どのようにユーザを認証するか」ということをガード(Guard)という概念で実装している。つまり、認証に関してカスタムしたいことがあればガードを自作し、それに基づいて認証を行うように設定すれば良い。
Laravelにはデフォルトのガードが二つ存在し、一つはSessionGuard
クラス、もう一つはTokenGuard
クラスである。それぞれデフォルトの web 用のガード、api 用のガードとして実装されている。そこで、それらのサブクラスを実装してデフォルトのGuardと入れ替えればさくっと認証処理をカスタムできる。便利だ。
と思っていたら、意外なところでハマったので、この記事はそれに関するレポートである。より具体的には、
Cookie jar has not been set
というエラーが出た時の対処法である。
#結論
上記のエラーに当たったということは基本的なGuardのカスタムはできていて、いざ認証しようとした時(特にRememberMe機能を使用しようとした時)に上記のエラーに当たったものと思われるので、解決策を先に述べる。
おそらくapp/Providers/AuthServiceProvider.php
のboot
メソッドを用いて自作のガード(CustomGuard
クラスとする。このクラスはSessionGuard
クラスを拡張しているものとする。)を以下のように登録していると考えられる。
public function boot()
{
$this->registerPolicies();
// 自作ガードの登録
Auth::extend('custom_guard', function($app, $name, array $config) {
return new CustomGuard($name, Auth::createUserProvider($config['provider']), $app['session.store']);
});
}
そしてこのcustom_guard
をconfig/auth.php
でガードのドライバーとして指定しているはずである。世の中のカスタムガード作成の指南書にはほぼこれに準ずることが書かれている。
この状態で問題の Cookie jar has not been set エラーが出た時には、boot
メソッド中の自作ガードの登録部分を以下のように変更する。
public function boot()
{
$this->registerPolicies();
// 自作ガードの登録
Auth::extend('custom_guard', function($app, $name, array $config) {
$guard = new CustomGuard($name, Auth::createUserProvider($config['provider']), $app['session.store'])
if (method_exists($guard, 'setCookieJar')) {
$guard->setCookieJar($app['cookie']);
}
if (method_exists($guard, 'setDispatcher')) {
$guard->setDispatcher($app['events']);
}
if (method_exists($guard, 'setRequest')) {
$guard->setRequest($app->refresh('request', $guard, 'setRequest'));
}
return $guard;
});
}
以上。
#やっていること
コードの中身を見てもらえれば一目瞭然であるが、変更前はただガードクラスを生成して返しているだけだが、変更後は生成したインスタンスのsetCookieJar
、setDispatcher
、setRequest
メソッドを呼び出してから返している。おそらく Cookie jar has not been set エラーはsetCookieJar
を呼び出すことによって解決する。
さて、この解決策がどこから来たかという話であるが、それはデフォルトのSessionGuard
を登録してるIlluminate\Auth\AuthManager
のresolve
メソッド及びそこから呼び出されるcreateSessionDriver
メソッド、callCustomCreator
メソッドを見ていくと解決する。
まずresolve
メソッドであるが、これはメソッド名の通りガード名(上記の例で言えばcustom_guard
)からガードクラスのインスタンスを解決するためのメソッドである。その中身を見てみると、処理は
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
という条件分岐によって、自作されたガードであるか否かで二つに分かれる。自作のガードであればメンバ変数$customCreators
配列にその名前が登録されているためcallCustomCreator
メソッドが呼ばれ、デフォルトのSessionGuard
やTokenGuard
を使用している場合には、それぞれcreateSessionDriver
メソッドやcreateTokenDriver
メソッドが呼ばれることになる。
callCustomCreator
メソッドとこのcreateSessionDriver
メソッドとを見比べてみると、前者はAuth::extend
メソッドに渡したコールバックを呼び出す、つまり変更前のコードであれば単にカスタムガードクラスを生成して返すだけなのに対し、後者は
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider'] ?? null);
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
// When using the remember me functionality of the authentication services we
// will need to be set the encryption instance of the guard, which allows
// secure, encrypted cookie values to get generated for those cookies.
if (method_exists($guard, 'setCookieJar')) {
$guard->setCookieJar($this->app['cookie']);
}
if (method_exists($guard, 'setDispatcher')) {
$guard->setDispatcher($this->app['events']);
}
if (method_exists($guard, 'setRequest')) {
$guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
}
return $guard;
}
のように記述されており、すなわち変更後のコードと同様生成したインスタンスに対して三つのメソッドを呼び出している。(というより、変更後のコードはAuthManager
のこのメソッドをコピーしただけである。)
三つのメソッドの中身は追いかけてはいないが、名前から察するに問題の Cookie jar has not been set エラーはsetCookieJar
メソッドの呼び出しに起因することは明らかである。
#まとめ
SessionGuard
のカスタム時に陥りやすいと思われるエラーの対処方法について解説した。おそらくTokenGurad
のカスタムも同じようなことが言えると思うが、どのようなエラーが出るのかは不明、対処方法は同じ(createTokenDriver
の処理を参考にすればよい。)と思われる。
#参考資料