本記事ではLaravelのsanctumを使用したSPA認証の概要と導入手順を説明していきたいと思います。
基本的には公式ドキュメントの流れで進めていきます。
環境
- windows10
- DockerDesktop for Win 3.5.x
- Laravel 8.x
- PHP 8.x
- node 14.17.4 or 16.13.1
- npm 6.14.14 or 8.1.2
- React 17.0.1
- VsCode
- gitbash 2.32.0.1
Laravel Sanctumとは
概要
- SPA (シングルページ アプリケーション)、モバイル アプリケーション、およびシンプルなトークン ベースの API 向けの超軽量の認証システムを提供してくれる仕組みです
- Sanctumではトークンでの認証とCookieを使った認証のどちらかを選ぶことができますが、
Cookieを使った認証が推奨されている模様なので、今回はこちらを紹介します。
簡単な違い
API Tokens
- Sanctumを介して、ユーザーにAPIトークンを発行する
- APIトークンは、db上のpersonal_access_tokensというテーブルで管理される
SPA Authentication
- Laravel標準のクッキーベースのセッション認証
- HasApiTokensトレイト、personal_access_tokensテーブルは不要
- SPAの認証には、CookieベースのSPA Authenticationが推奨されている模様
導入手順
1.sanctumのインストール
composer require laravel/sanctum
2.sanctum設定ファイルと移行ファイルを公開
以下コマンドを実行し、sanctumで利用するconfig設定ファイルと、sanctum用のテーブルを作成するマイグレーションファイルを作成します。
- config/sanctum.php
- database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
こちらは、トークンでの認証のみ必要なテーブルなので削除してもOK
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
3.ミドルウェアの追加
Sanctumのミドルウェアをアプリケーションのapp/Http/Kernel.phpファイル内の、apiミドルウェアグループに追加する
app/Http/Kernel.php
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // here
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
EnsureFrontendRequestsAreStatefulクラスは、ざっくりと以下のような処理を行う
- セッションクッキーのセキュア設定を強制
cookie のスコープ(参照・操作の権限)を HTTP リクエストに制限し、javascriptなどから直接参照・操作されないようにする - クッキーの暗号化
- レスポンスにクッキー付与(\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class)
- セッションの開始
- laravel_sessionをレスポンスに追加
- CSRFトークンを検証する
4.ドメイン設定
- sanctum.phpに、クッキーを使用した「ステートフル」な認証を維持する必要があるドメイン(フロントエンドサーバーのドメインでOK)を指定
- デフォルトでlocalhostが設定されているので、特殊なことをしていない限り特にローカル環境で開発するだけなら追加する必要はなし
- 本番にリリースする際は必ず本番のサイトドメインを指定する必要あり
※.envのSANCTUM_STATEFUL_DOMAINSに設定するか、このファイルに直接ドメインを記載する必要があります。(''の中に複数指定可)
config/sanctum.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) : ''
))),
5.CORS(クロスオリジンリソースシェアリング)
CORS(Cross-Origin Resource Sharing)とは、WebブラウザがHTMLを読み込んだ以外のサーバからデータを取得する仕組みです。
※異なるオリジン間で通信する場合に必要(例えば、ローカルのDockerでフロントエンドはlocalhost:3000でNext.jsを起動し、バックエンドはlocalhost:9000で起動している等)
- config/cors.phpのsupports_credentialsオプションをtrueに
- 'paths' に 'sanctum/csrf-cookie'を追加
config/cors.php
<?php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'], // corsを許可するパスの設定
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true, // クッキー認証の許可
];
また、axiosを使用している場合は、withCredentials: trueを設定します
resources/js/bootstrap.js
window.axios.defaults.withCredentials = true;
もしくは、専用のファイルを作成し、使用するところで、importして使う
frontend\next-web\src\libs\axios.tsx
import Axios from 'axios'
const axios = Axios.create({
baseURL: 'http://localhost:9000',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
withCredentials: true
})
export default axios
6.サブドメインでセッションを共有したい場合
アプリケーションのセッションクッキードメイン設定で、ルートドメインのサブドメインを指定する
(.envのSESSION_DOMAINでもOK)
SESSION_DOMAINには、ドメインの異なるサイトでセッションを共有したい場合にそのドメインを記述する(片方でログインしたらもう一方でもログイン状態を維持したいみたいな内容)
sample01.hoge.com
sample02.hoge.com
例えば上記のようなサイトがあった場合、以下のように指定すると、双方のドメインで認証のセッションを共有することができる
config/session.php
'domain' => '.hoge.com',
7.セッション管理
.envファイルのSESSION_DRIVER=の値はデフォルトだと「file」ですが、これを「cookie」にすることでsanctum認証用のトークンをcookieで管理することができますし、「database」にすることでdatabaes管理にすることができます。(他にも管理方法はあるので、詳細は公式を確認ください。)
.env
SESSION_DRIVER=file
# SESSION_DRIVER=cookie
# SESSION_DRIVER=database
管理をdatabaseにした場合は以下コマンドで専用のテーブルを作成する必要があります。
php artisan session:table && php artisan migrate
認証の流れ
1.フロントエンドからリクエストを投げる
CSRF対策
- 通常のFormでも対策が必要なように、SPA認証でもCSRF対策は必要になります。
- Laravel Sanctumをインストールした時点で専用のエンドポイント(/sanctum/csrf-cookie)が追加されており、認証処理を行う前にこのエンドポイントにリクエストを投げて、アプリケーションのCSRF保護を初期化する必要がある
- 上記のリクエストを受けて、Laravelは現在のCSRFトークンを含むXSRF-TOKENをクッキーとしてセットする
- このクッキーに入っているトークンをX-XSRF-TOKENヘッダに入れてSPA側からリクエストする必要がある(これらの一連の処理はAxiosやAngularなどの一部のHTTPクライアントライブラリでは自動的に行ってくれる)
/resources/js/components/auth/Login.jsx
const signIn = () => {
// ログイン処理前にCSRFトークンを初期化
axios.get("/sanctum/csrf-cookie").then((response) => {
axios
.post("/api/login", {
email,
password,
})
...割愛
2.API(バックエンド)のルート保護とレスポンス
sanctum認証ガードによるルート保護
- 認証済みでないと許可したくないルート(ユーザー認証が必要なページ等)は、sanctum認証ガードにより保護できる
- 設定方法は、routes/api.phpファイル内のAPIルートに認証ガードを指定するだけでOK
- ガードで保護されたルートは、ユーザーのセッションが期限切れになった場合、後続のリクエストは401か419HTTPエラーを返却する
routes/api.php
// 認証前でもOK
Route::post("/login", [LoginController::class, "login"]);
Route::post("/logout", [LoginController::class, "logout"]);
Route::post("/register", [LoginController::class, "register"]);
// 認証済みでないと許可しない
Route::group(["middleware" => ["auth:sanctum"]], function () {
Route::get("/posts", [PostController::class, "index"]);
Route::get("/posts/{postId?}/comments", function (Request $request) {
...割愛
});
});
3.認証
- 以下はLoginControllerを簡単に作成してみたもの
- LoginControllerのサンプル
https://laravel.com/docs/8.x/authentication#authenticating-users
Auth::attemptで、フォームから送信されてきた情報をもとにデータベースをチェックし、該当のユーザー情報がデータベースに存在していればtrueを返し、ログイン済みとします。そして返り値として、該当ユーザーの情報をjsonレスポンスとして返します。
app/Http/Controllers/Auth/LoginController.php
class LoginController extends Controller
{
public function login(Request $request)
{
$credentials = $request->validate([
"email" => ["required", "email"],
"password" => ["required"],
]);
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return response()->json(Auth::user());
}
return response()->json([], 401);
}
...割愛
4.レスポンスを受け取って処理
- レスポンスを受け取ったフロントエンドでは下記のように、上記で受けた返り値をresで受け取って、reduxのストアーのstateを更新する等して、ストアーで状態管理を行うための処理を用意する必要があります(ここは色々やり方があるかと思います。)
- 認証処理が完了したら、最終的に指定したトップページへ遷移するという流れ
※operationsはreduxフローの非同期処理を行う部分です。Login.jsx等のログインコンポーネントからこの関数を呼び出して非同期で処理を行います。
resources/js/reducks/users/operations.js
axios.get("/sanctum/csrf-cookie").then((response) => {
axios
.post("/api/login", {
email,
password,
})
.then((res) => {
dispatch(
signInAction({
uid: res.data.id,
username: res.data.name,
isSignedIn: true,
})
);
dispatch(push("/calendar"));
})
.catch((err) => {
console.log(err.response);
});
});
以上です。慣れると比較的簡単ではあるのですが、初めて実装するとなると少しつまづく点もありそうですね。本記事を参考にしてぜひご自身の開発に取り入れてみてください。
まとめ
いかがでしたでしょうか。本記事では、SPA (シングルページ アプリケーション)、モバイル アプリケーション、およびシンプルなトークン ベースの API 向けの超軽量の認証システムを提供してくれる仕組みであるlaravel-sanctumを活用してcookieベースでAPI認証を実装する方法について解説してみたいと思います。