どうも、シローです。
今回はフロントサイドをNext.jsで実装し、LaravelをAPIサーバのみとして利用する場合、
認証部分はLaravel Sanctumを使うと良いよという内容です。
Laravel Sanctumとは
Laravel Sanctumはざっくりといえば、LaravelをAPIサーバとして運用する想定をサポートした認証システムです。
2つの機能があり
Authorization
ヘッダーにAPI Tokenを付与する- SPAで構築したフロントサイドとのアプリケーションをcookieベースの認証を行う
ことができます。
なので、クライアントアプリをモバイルアプリ、Next.jsのようなSPAで実装して、
データのやり取り部分はLaravelをAPIサーバとして利用するケース場合に利用するのが良いと思います。
SPAの認証について
クライアントサイドとサーバサイドを別々で実装する場合、
- クライアントサイド:sample-service.com
- サーバサイド:api.sample-service.com
のようにドメインが異なる場合があります。
この場合、XSSとかCSRFのようなセキュリティ的な対策のほか、CORSポリシーに引っかかるのでリクエストを正常に処理することが必要になってきます。
Laravel Sanctumでは、これらの対策をcookieを用いた認証機能でサポートしています。
初期設定(SPA認証をする場合)
- パッケージをインストール:
composer require laravel/sanctum
- 設定ファイル(config/sanctum.php)を作成:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
- API Tokenを保存するテーブルを作成:
php artisan migrate
- apiルーティング(app/Http/Kernel.php)にSanctumのミドルウェアを使用するように設定
app/Http/Kernel.php
1 2 3 4 5 |
'api' => [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], |
認証部分を実装
SanctumでAPIベースでユーザの認証を実装する例を紹介します。
設定ファイル(config/sanctum.php, config/cors.php, config/session.php)を編集
フロントサイドが別ドメインになっている場合、CORSポリシーによってリクエストが弾かれたりCookieが無効になるので設定ファイルを編集してやる必要があります。
今回は新規開発サービスを例として
- フロントサイド:netaverse.local:3000
- サーバサイド:api.netaverse.local
という設定で進めていきます。
config/cors.phpのstatefulにフロントサイドのドメインを追加する
.envにSANCTUM_STATEFUL_DOMAINS
というキーにフロントサイドのドメイン(ポート番号を含む)の値を設定します。
.env
1 |
SANCTUM_STATEFUL_DOMAINS=netaverse.local:3000 |
config/sanctum.phpのstateful
というキーに、この設定値を読み込むように設定します。
config/sanctum.php
1 2 3 4 5 6 7 |
<?php ... 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' ))), |
config/cors.phpに許可するオリジンやリクエストメソッド、ヘッダーの設定をする
異なるドメインからのリクエストを許可するために、.envとconfig/cors.phpに次のような設定をいれます。
- .envにフロントサイドのドメインを
FRONT_SERVICE_HOST
というキーで設定 - config/cors.phpを以下のように設定
.env
1 |
FRONT_SERVICE_HOST=http://netaverse.local:3000 |
config/cors.php
1 2 3 4 5 6 7 8 9 10 11 |
<?php ... 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], 'allowed_origins' => [env('FRONT_SERVICE_HOST')], 'allowed_origins_patterns' => [], 'allowed_headers' => ['Accept', 'Content-Type', 'Origin', 'X-XSRF-TOKEN'], |
config/session.phpにCookieを有効にするドメインを追加する
最後にCookieをサーバサイドで受け取ってユーザのセッション情報を取得できるようにconfig/session.phpのdomain
というキーにトップドメインに「.」を追加したサブドメインを許容した値を設定します。
- トップドメイン:netaverse.local
- サブドメイン:api.netaverse.local
.env
1 |
SESSION_DOMAIN=.netaverse.local |
config/session.php
1 2 3 |
<?php ... 'domain' => env('SESSION_DOMAIN', null), |
フロントサイドのaxiosの設定
クロスオリジンでCookieベースのユーザ認証をするにはAccess-Control-Allow-Credentials
ヘッダーにtrue
を入れる必要があります。
また、CSRF保護を適用するためにsanctumが用意したエンドポイント(/sanctum/csrf-cookie)を事前に実行する必要があります。
.env
1 |
NEXT_PUBLIC_API_BASE=http://api.netaverse.local |
utils/axios.ts
1 2 3 4 5 6 |
import axios from 'axios' export default axios.create({ baseURL: process.env.NEXT_PUBLIC_API_BASE, withCredentials: true }) |
api/csrfCookie.ts
1 2 3 4 5 6 7 8 9 10 |
import axios from 'utils/axios' export const api = async () => { try { const { data } = await axios.get('/sanctum/csrf-cookie') return data } catch (e) { throw e } } |
ログイン部分の実装はこんな感じです
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import { api as csrfApi } from 'api/csrfCookie' import { validation, api as loginApi } from 'api/userLogin' const LoginForm: React.FC<Props> = (props: Props) => { const form: Form = { email: "", password: "", remindLogin: false } const [apiError, setApiError] = useState<string>('') const router = useRouter() const handleSubmit = async (values: Form) => { const data = await csrfApi() try { await loginApi(values) } catch (e) { if (Axios.isAxiosError(e) && e.response) { const { status } = e.response if (status === 401) { setApiError('メールアドレスまたはパスワードが間違っています') } } else { setApiError('システムエラーが発生しました') } throw e } window.location.href = '/' } |
認証が必要なルーティングにミドルウェアを適用する
ユーザがログイン済みの場合のみにアクセスできるAPIを設計したい場合はミドルウェアのauth:sanctum
を使用すると良いです。
auth:sanctum
の中身はデフォルトのweb
の認証ガードを使用した、Cookieベースのユーザ認証と同じになります。
routes/api.php
1 2 3 4 5 |
<?php ... Route::middleware('auth:sanctum')->group(function () { Route::get('/me', [UserController::class, 'getMe']); }); |
まとめ
- SanctumはSPA認証時のクロスドメインの制約やCSRFやXSSといった対策をおこなってくれる
- サーバサイドの設定方法はconfig/cors.php, config/sanctum.php, config/session.phpを編集するのみ
- フロントサイドではリクエスト前に/sanctum/csrf-cookieを実行する必要あり
- ユーザ認証のミドルウェアでauth:sanctumが使える