概要
Laravel SanctumはSPAやスマホアプリなどで利用するバックエンドWeb API用の認証の仕組みを提供していくれるが、いまいち何をしてくれるのか分かりづらかったので、ソースコードを紐解いて具体的に何をしているのか調べてみた。
SanctumにはAPIトークン認証とSPA認証の2つの方式がある。
- APIトークン認証
- 認証後、トークンを発行しアプリに渡し、アプリはそのトークンを使用してプライベートなWeb APIにアクセスする。主にスマホアプリなどで利用する。
- SPA認証
- 認証後、アプリにトークンなどを渡すこと無く、そのWeb APIのドメインのCookieに認証済みのセッションを記録する。クライアントアプリに秘匿情報が渡らないので、よりセキュアな仕組みとなる。
今回はSPA認証について説明する。
Sanctumがやってくれること3つ
- Web APIのエンドポイントをステートフルにしてくれる。
- Guardを実装してくれる。
- CSRFトークンを提供するAPIを実装してくれる。
Sanctumがやらないこと
- ログインページなどのルーティングやフロントページの実装
必要な場合は、スキャフォールディングや、Fortifyで実装したりする。 - ユーザプロバイダの実装
特別な要件がない限り、Laravelに既存のdatabaseとeloquentのいずれかを利用するのが一般的。
Sanctumがやってくれること詳細
まず、基礎を知るために下記2つを読んでおくことをお薦めする。
- Laravel公式ドキュメント - Sacntum SPA認証 は大雑把な内容だが、Sactumをどう使うべきかを示している。
- Laravel Sanctum Explained : SPA AuthenticationはSanctumがしていることをわかりやすく解説している。
1. Web APIのエンドポイントをステートフルにしてくれる
Laravelのデフォルトauthでは、Webページと異なりWeb APIのエンドポイントはステートフルではない(セッションが作成されない)ので認証でアクセスを制限できない。ステートフルではないと言うことは、サーバ側で(認証を含め)状態を維持しないと言うことなので、認証状態をアクセス制限に使用することができない。
Sanctumは、Web APIにも「必要に応じてセッションを作成する」機能を提供する。
app/Http/Kernel.phpを見ると、Laravelのデフォルトではroute/web.phpに記述したエンドポイントにはStartSessionなどのミドルウェアが適用されるのでステートフルになる。app/Http/Kernel.phpのapiミドルウェアグループにSanctumのEnsureFrontendRequestsAreStatefulクラスを追加することで、これをやってもらう。
SanctumのEnsureFrontendRequestsAreStatefulの実装を見ると、やっていることはwebミドルウェアグループとほとんど同じミドルウェアを適用することだが、webミドルウェアグループとの違いは、SANCTUM_STATEFUL_DOMAINSに列挙したドメインからのアクセスに限定するというところになる。(Web APIへのリクエストのrefererまたはoriginヘッダーに設定されたドメインが、SANCTUM_STATEFUL_DOMAINSに含まれているかどうかをチェックしている。)それ以外のドメイン(localhostなどはデフォルトで追加される)からのアクセスではセッションが使用されずステートフルにならない。これにより、意図しない外部サイトがログイン状態を使用して我々のWeb APIにアクセスしてしまうのを防止する。
ちなみに、SanctumのEnsureFrontendRequestsAreStatefulではエンドポイントをステートフルにしているだけで、アクセス制限をかけているわけではない。アクセス制限は、更にauthミドルウェアを通すことで実現する。(このauthミドルウェアが機能するためにはステートフルである必要がある。)
2. Guardを実装してくれる
sanctumという名称のGuardを実装してくれる。Guardに関しては特に設定は不要で、Route::middleware('auth:sanctum')のようにauthミドルウェアを使用する際にこのガードを指定できる。
Guardの実装はconfig/auth.phpなどで定義する必要はなく、自動的に作成&挿入してくれる。
sanctumガードがすることは、大まかに下記2点
-
config/sanctum.phpでguardで指定するガードドライバを使用する - APIトークンを使用している場合に、トークンをチェックする(SPA認証なので、無視)
config/sanctum.phpでのguardが指定されていなければ、webガードが使用される1。そして、特別な理由がない限り指定する必要はないので、結局config/auth.phpのguardsに定義されているwebガードが使用される。
このためSanctumのデフォルトのガード設定だとauth:sanctumとauth:webに違いが無いように見える(実際にはあるかもしれない)が、保守性を考慮してもauth:sanctumを使用しない理由はない。
また、公式ドキュメントによれば、routes/web.phpでのアクセス制限もauth:sanctumを使用する必要があるようだ。
3. CSRFトークンを提供するAPIを実装してくれる
/sanctum/csrf-cookieというエンドポイントが自動的に作成される。SanctumServiceProviderが動的に挿入している。
このエンドポイントにアクセスすると、レスポンスボディではなくXSRF-TOKENというCookieに乗ってくるので、フロントエンドではこれを抽出して使用する。これはAxiosなどの流儀に従っているため、Axiosは自動的に抽出してX-XSRF-TOKENというヘッダーに設定してくれる。
CSRFトークンを使用するわけ
Web APIエンドポイントはSameOriginPolicyによりCORS設定で許可されていない外部サイトがJavaScriptなどから利用できないように保護されている。しかし、リクエストができないわけではなく、レスポンスが使用できないだけなので、UPDATEやDELETEなどのHTTPメソッドがリクエストできてしまう。そのリクエストのドメインはリクエスト先のものであるためブラウザが認証済であれば、そのリクエストは実行されてしまう。これを防止するために、そのリクエストが許可されたサイトからのものかを確認するためにCSRFトークンが利用される。
CSRFトークンも同様に誰でもリクエストできてしまうが、CORS設定で許可されていないサイトではレスポンスを受け取れないため、このCSRFトークンを取得できない。よって、CSRFトークン付きで送られてくるリクエストはCORSで許可されたサイトからのリクエストであることが保証される。
CSRFに関しては下記記事がとても良く説明してくれている。感謝!
以上!
LaravelやSanctumが色々とやってくれるとは言え、システムによって認証の要件もまちまち(例えばSSOしたい!とか)なので、具体的にSanctumが何をどうしてくれるのか理解していないと、実用的なシステムを構築するのは難しい。
-
Sanctumのガードは
SessionGuardなどと同じ\Illuminate\Contracts\Auth\Guardインタフェースを実装した\Illuminate\Auth\RequestGuardで、\Laravel\Sanctum\SanctumServiceProvider::createGuardでインスタンス化されている。このガードインスタンスはcallbackが呼ばれると、実際には\Laravel\Sanctum\Guard::__invokeを呼び出す。この__invokeはGuard::user()から呼び出され、認証済みのユーザを返すとこが期待されている。__invokeの中では、更にconfig('sanctum.guard', 'web')で取得したガードを使用してUserモデルを取得している。つまり、sanctum.guard.providerにuser providerを指定しない限り、webガードを使用してUserモデルを使用している。そのため、特に指定しなければwebに指定したUser providerが使用されることになる。 ↩