Sanctum の仕組み
@ucan-lab さんの記事で結構詳しく書かれているのでまずはこちらを参考のこと
Cookie 発行の条件
Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful
が特定条件でCookieを暗号化したり発行したりしてくれます。その特定条件とは
- refererヘッダー が存在し、その値が
sanctum.php
で指定したドメインに含まれる。 - ↑がなければ originヘッダーが存在し、その値が
sanctum.php
で指定したドメインに含まれる。
です。
その判定は上記ファイル内の fromFrontend()
メソッドで行われています。
sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php
public function handle($request, $next)
{
$this->configureSecureCookieSessions();
return (new Pipeline(app()))->send($request)->through(static::fromFrontend($request) ? [
function ($request, $next) {
$request->attributes->set('sanctum', true);
return $next($request);
},
config('sanctum.middleware.encrypt_cookies', \Illuminate\Cookie\Middleware\EncryptCookies::class),
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
config('sanctum.middleware.verify_csrf_token', \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class),
] : [])->then(function ($request) use ($next) {
return $next($request);
});
}
// Cookieを付与するか判定するメソッドはこれ
public static function fromFrontend($request)
{
$domain = $request->headers->get('referer') ?: $request->headers->get('origin');
if (is_null($domain)) {
return false;
}
$domain = Str::replaceFirst('https://', '', $domain);
$domain = Str::replaceFirst('http://', '', $domain);
$domain = Str::endsWith($domain, '/') ? $domain : "{$domain}/";
$stateful = array_filter(config('sanctum.stateful', []));
return Str::is(Collection::make($stateful)->map(function ($uri) {
return trim($uri).'/*';
})->all(), $domain);
}
Referer ヘッダ、 Origin ヘッダが付与されない
特定のブラウザで、 OAuth のフロー内でリダイレクトされてAPIにリクエストが飛んでくる場合などに、 referer ヘッダが付与されない状態でリクエストが飛んでくる場合があるそうです。
そうした場合、通常であれば OAuth の認証をもとに Cookie を発行したいという要望が答えられませんが、上記のファイルを継承させて、オーバーライドすれば対応可能です。
やること
-
sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php
を継承したファイルを作成し、fromFrontend()
をオーバーライドする - 新しく設定した条件に脆弱性がないか十分に検討する。
-
Kernel.php
を api の middleware に登録する。
その他
Auth::guard('sanctum') の実態
最後の行だけ読めばOKです。
- 実態としては
Illuminate\Auth\RequestGuard
- ミドルウェアを挟んだ場合に配下のリクエストで必ず呼ばれる
check()
メソッドがあり、その中でRequestGuard
のuser()
メソッドが呼ばれるが、user()
メソッド内で sanctum のGuard.php
が__invoke()
され、その際にAuth::guard('web')->user()
が呼び出され return される。 - なので、
Auth::guard('sanctum')->user()
をするとAuth::guard('web')->user()
の結果が返ってくる。