SPA認証方法 @Laravel & Vue-Router


{success} 本システムにおいてのGuard方法を記載します。


概要

本システムはLaravel VueでのSPA方式で構築を行うため、

毎ページの認証方法が既存の方法とはことなる。

そのため、認証方法を明記します。フロントエンド、バックエンド、共に作業が必要です。


公式ドキュメント

https://readouble.com/laravel/8.x/ja/sanctum.html

https://github.com/laravel/sanctum

https://laravel.com/docs/8.x/sanctum

https://laravel-news.com/tag/sanctum

https://router.vuejs.org/ja/guide/advanced/navigation-guards


参考サイト

https://qiita.com/ucan-lab/items/3e7045e49658763a9566

https://github.com/ucan-lab/laravel-sanctum-tutorial

https://qiita.com/koduki/items/b4b56a27c6b7406a4ddb


サンプル

https://github.com/ucan-lab/laravel-sanctum-tutorial


利用機能・ライブラリ

パッケージ名 機能・ライブラリ 機能概要
Laravel Sanctum Cookie・Session認証を行う
Vue vue-router SPAを機能させる際に利用する
Vue Vuex4.0 認証状態保持に利用する

{danger}TOKENを使わない理由:https://qiita.com/nyandora/items/8174891f52ec0ea15bc1

有効期限決めればいいんじゃないかと思うがそれは一先ず忘れることにする。(Laravel以外だとやってる場合ある)


仕組み

APIリクエスト

  • ログイン
  • SESSION・COOKIE認証(多分SESSIONにログイン済み情報として登録。そのTOKENがCOOKIEに入ってる?)
  • APIリクエスト時に、cookieに問合せるAPIを実行してから全てのAPIを実行(認証が必要なAPIのみ)
  • cookieに問い合わせるとcookieTOKENがかえってくるのでそれをheaderにセットして認証をする
  • 認証に失敗(時間切れ等)すると401/419コードが返ってくるようになる
    • ログイン画面へ遷移/vuexリセット

ルート保護

  • ログイン
  • ログイン結果をvuexで保持
  • vue-routerで遷移する際にログイン状態がfalseだったらログインページへ遷移する処理
  • vue-routerでOKだが上記APIリクエストで失敗したとき(恐らくログイン後、放置して長時間後に操作した場合)
    • 結局APIで失敗してログイン画面へ戻ってくる
    • ログイン画面へ遷移/vuexリセット

構築手順

1

Sanctumインストール

composer require laravel/sanctum

2

Composerパッケージマネージャーを介してLaravel Sanctumをインストールします。

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

3

下記のファイルが生成されます。今回はAPIトークンを利用しないので削除してokです。

database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php

4

apiミドルウェアグループに\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStatefulミドルウェアを追加します。

{primary}FILE:app/Http/Kernel.php

protected $middlewareGroups = [
    'api' => [
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // 追記
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

5

SPA(Vue, React等)からリクエストを行うドメインを設定する必要があります。

config/sanctum.php の stateful に設定されています。

.envファイルのドメインとポート番号の設定が必要となります(本番環境のみ/ローカルは不要)

{primary}FILE:.env

SANCTUM_STATEFUL_DOMAINS=api.example.com:443

6

EnsureFrontendRequestsAreStateful の説明

  • セッションクッキーのセキュア設定を強制
  • refererヘッダーまたはoriginヘッダーのチェック
    • SPAからこのヘッダーが送られないと認証エラーになる
    • Postmanで動作確認するときに注意する
  • web ミドルウェアグループで使用しているの一部のミドルウェアを導入
    • クッキー暗号化ミドルウェア(\App\Http\Middleware\EncryptCookies)
    • Cookieを暗号化する
    • 攻撃者がCookieにアクセスできたとしても、その内容を変更するとCookieが返送されたときにサーバーによって拒否する
    • レスポンスにクッキー付与(\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse)
    • Cookieファサードでキューに入れられたCookieを処理する
    • セッション有効化(\Illuminate\Session\Middleware\StartSession)
    • Laravelセッションとセッションクッキーを設定し、レスポンスに追加する
    • CSRFトークン検証(\App\Http\Middleware\VerifyCsrfToken)
    • CSRFトークンがすべて正常であることを確認する
vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php

別のドメインから動作確認等する場合に必要な処理です。(POSTMAN使うときとか)

別のドメインからのアクセスを許可する設定が必要です(CORS)

Session・Cookieの設定も必要らしい

※ドメインの先頭に . を付けます。

null の場合、サブドメイン間でCookieの共有ができません。

リクエスト時、withCredentials が必要です

まずはconfigファイルを作成します。

composer require fruitcake/laravel-cors
↓
app/Http/Kernel.phpに下記のコードを追記します。

    protected $middleware = [
        \Fruitcake\Cors\HandleCors::class,
        // ...
    ];
↓
php artisan vendor:publish --tag="cors"

コンフィグファイルはconfig/cors.phpに作成されます。

{primary} FILE:config/cors.php

'supports_credentials' => true,

{primary} FILE:.env

SESSION_DOMAIN=.example.com

7

SPAを認証するには、SPAの「ログイン」ページで最初に/sanctum/csrf-cookieエンドポイントに

(Laravel Sanctumをインストールした時点で下記のエンドポイントが追加されています)

リクエストを送信して、アプリケーションのCSRF保護を初期化する必要があります。

このエンドポイントにGETリクエストするとCSRFトークンを含むXSRF-TOKENクッキー付きレスポンスが返却されます。

このクッキーに入っているトークンを X-XSRF-TOKEN ヘッダに入れてSPA側からリクエストする必要があります。

(AxiosやAngular HttpClientなどの一部のHTTPクライアントライブラリでは自動的に行います。)

  • ユーザーのセッションが期限切れになった場合、後続のリクエストは401か419HTTPエラーを返却する
    • 401, 419エラーが出た場合はSPA側でログインページにリダイレクトする必要がある
axios.get('/sanctum/csrf-cookie').then(response => {
    // API処理...
})

8

APIルートにmiddlewareを設定します。

{primary} routes/api.php

use Illuminate\Http\Request;

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

9

セッションの保存先を設定できます。

デフォルトはfileです。

セッションドライバーを cookie に変更してクライアント側でクッキーにセッションを入れて対応します。

セッションドライバーを cookie にする場合ですが、クッキーの機能として容量は4Kバイトまでで20個までしか保存できない制約があります。

認証情報以外にも何かしらの値をクッキーに保存していくとすぐ制約に引っかかってしまいます。

その場合はRedisをセッションドライバーとして利用すると良いでしょう。

{primary} FILE:.env

SESSION_DRIVER=cookie

10

実装方法です。

corsファイルに追加 / ルート設定 / API作成

{primary} FILE:config/cors.php

'paths' => [
    'api/*',
    'login', // 追加
    'sanctum/csrf-cookie',
],

{primary} API例

use Exception;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
public function login(Request $request): JsonResponse
{
    $credentials = $request->validate([
        'email' => ['required', 'email'],
        'password' => 'required',
    ]);

    if ($this->getGuard()->attempt($credentials)) {
        $request->session()->regenerate();

        return new JsonResponse(['message' => 'ログインしました']);
    }

    throw new Exception('ログインに失敗しました。再度お試しください');
}

{primary} POSTMAN等の外部で操作する場合の例とルート保護これから