概要
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が使用されることになる。 ↩