LoginSignup
6
7

More than 1 year has passed since last update.

Laravel+Nuxt 環境で sanctum の SPA(クッキー)認証する

Posted at

# 概要

この記事は、Laravel+Nuxtでsanctum認証を実装時の自分用の備忘録になります。

環境

  • mac M1
  • Laravel:8.63.0
  • Laravel Sanctum:2.11
  • Nuxt:2.15.8

インストール(Laravel)

作業ディレクトリを適当な場所に作成する。

mkdir laravel-nuxt

Laravel プロジェクトの作成
特にバージョンにこだわりなかったので、最新の8系をインストール。

composer create-project laravel/laravel

sanctum のインストール

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

database の準備
手軽に確認できるためsqliteを使用しました。
環境に合わせて適宜修正してください。

touch database/database.sqlite

.env ファイル修正

DB_CONNECTION=sqlite // 修正
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

ログイン実装で使用するUserテーブルを作成したいため、migrationを実行します。

php artisan migrate

Cookie 認証

Sancatum のミドルウェアをapp/Http/Kernel.phpapiグループに追加します。
これにより Laravel のセッションクッキーを使用して認証することができます。

app/Http/Kernel.php
class Kernel extends HttpKernel
{
    protected $middlewareGroups = [
        'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // 追記
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
}

CORS とクッキー

別のサブドメインで実行する場合Access-Control-Allow-Credentialsヘッダをtrueで返す必要があるため、以下を設定します。

config/cors.php
    'supports_credentials' => true,

SPA 認証の実装

SPA 認証するには CSRF 保護を初期化し、ログイン処理をする必要があります。
CSRF 保護を初期化するには/sanctum/csrf-cookieエンドポイントにリクエストを送信する必要があるので注意しましょう。

axios.get('/sanctum/csrf-cookie').then(response => {
    // ログイン…
});

またログインやログアウト処理は Laravel の公式曰く'web'ルートに作成する必要がある。
Laravel 8.x Laravel Sanctum

ログインコントローラーの作成を行う。

php artisan make:controller LoginController

ルーティングの設定を行う。

routes/web.php
<?php

use App\Http\Controllers\LoginController;

Route::post('/login', [LoginController::class, 'login']);
Route::post('/logout', [LoginController::class, 'logout']);

config/cors.phploginlogoutのパスを追加。

config/cors.php
    'paths' => [
        'api/*',
        'login', // 追加
        'logout', // 追加
        'sanctum/csrf-cookie',
    ],

LoginControllerの実装をする。公式のユーザーを手動で認証するコードの return を少し変更します。

LoginController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use SebastianBergmann\Environment\Console;

class LoginController extends Controller
{
    /**
     * Handle an authentication attempt.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    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(['message' => 'ログインに失敗しました'], 401);
    }

    public function logout(Request $request)
    {
        Auth::logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return response()->json(['message' => 'ログアウトしました']);
    }
}

またapi.phpのコードを一部修正します。

routes/api.php
use Illuminate\Support\Facades\Auth;

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

クッキー認証の実装は完了です。次にテスト用のユーザを作成します。

$ php artisan tinker

App\Models\User::factory()->create(['email' => 'hoge@example.com']);
exit

Laravel8 系のデフォルトパスワードはpasswordです。
Laravel の実装はこれで終了です。
次に Nuxt 側の実装を行っていきます。

インストール(Nuxt)

Nuxt プロジェクトの作成

yarn create nuxt-app nuxt

初期設定はSSR設定とaxiosモジュールを選択します。あとはお好みで選択してください。

axios で CORS の設定

nuxt.config.jsに以下を追記します。
これによってaxioswithCredentialsオプションをtrueにします。

nuxt.config.js
  axios: {
    baseURL: 'http://localhost:8000',
    credentials: true,
  },

ログインした管理ユーザーを保存する Store を作成する。

ログインユーザーを管理するため、store/auth.jsを作成します。
ログインの処理をする前に/sanctum/csrf-cookieにアクセスし,CSRF 保護を初期化する必要がありますので注意してください。

store/auth.js
export const state = () => ({
  authUser: null,
})

export const mutations = {
  setAuthUser(state, authUser) {
    state.authUser = authUser
  },
}

export const actions = {
  async login({ commit }, { email, password }) {
    await this.$axios.get('/sanctum/csrf-cookie').then(async (res) => {
      const response = await this.$axios
        .$post('/login', { email, password })
        .catch((err) => {
          console.log(err)
        })
      commit('setAuthUser', response)
    })
  },
  async logout({ commit }) {
    await this.$axios
      .$post('/logout')
      .then((res) => {
        console.log(res)
      })
      .catch((err) => {
        console.log(err)
      })
    commit('setAuthUser', null)
  },
}

API からログイン中の管理者ユーザを取得するようにする

Store のデータはリロードすると消えてしまうため、リロードする度にログインする必要があります。
そのため画面表示前にログイン中のユーザー情報を取得して Store にセットする処理が必要になります。
SSR から Cookie を取得するためにcookieparserモジュールを追加する。

yarn add cookieparser

store/index.jsを追加し、以下を記述する。

store/index.js
const cookieparser = process.server ? require('cookieparser') : undefined

export const actions = {
  async nuxtServerInit({ commit }, { req, app }) {
    cookieparser.parse(req.headers.cookie)

    await app.$axios
      .$get('/api/user')
      .then((authUser) => {
        commit('auth/setAuthUser', authUser)
      })
      .catch((err) => {
        console.log(err)
        commit('auth/setAuthUser', null)
      })
  },
}

nuxtServerInit は SSR 時に実行される処理で、
Cookie が有効な間はログイン中の管理者ユーザーの情報が毎回セットされるようになります。

ログイン中かをチェックして画面遷移させるミドルウェアを作成する

middleware配下にunAuthenticated.jsを作成します。
これにより Store に state.auth.authUser に設定されていれば、管理画面に遷移させます。

unAuthenticated.js
export default function ({ store, redirect }) {
  if (store.state.auth.authUser) {
    return redirect('/admin')
  }
}

middleware 配下にauthenticated.jsを作成し
これにより Store にstate.auth.authUserに設定されていなければ、ログイン画面に遷移します。

authenticated.js
export default function ({ store, redirect }) {
  if (!store.state.auth.authUser) {
    return redirect('/admin/login')
  }
}

ログイン後の画面を作成する

pages/admin/index.vueファイルを作成する。

pages/admin/index.vue
<template>
  <section>
    <h1>管理画面へようこそ</h1>
    <button @click="logout">ログアウト</button>
  </section>
</template>

<script>
export default {
  middleware: 'authenticated',
  methods: {
    async logout() {
      await this.$store.dispatch('auth/logout', null)
      this.$router.push('/admin/login')
    },
  },
}
</script>

ログイン画面を作成する

pages/admin/login.vueを作成する。

pages/admin/login.vue
<template>
  <section>
    <h1>ログイン</h1>
    <form @submit.prevent="submit">
      <div>
        <label for="email">email</label>
        <input type="text" id="email" v-model="email" />
      </div>
      <div>
        <label for="password">password</label>
        <input type="password" id="password" v-model="password" />
      </div>
      <button type="submit">login</button>
    </form>
  </section>
</template>

<script>
export default {
  middleware: 'unAuthenticated',
  data() {
    return {
      email: '',
      password: '',
    }
  },
  methods: {
    async submit() {
      await this.$store.dispatch('auth/login', {
        email: this.email,
        password: this.password,
      })
      this.$router.push('/admin')
    },
  },
}
</script>

動作確認

アプリを起動して、動作確認します。
まずLaravel(API)サーバーを起動します。

php artisan serve

次に Nuxt アプリを起動する。

yarn run dev

起動が完了後、http://localhost:3000/admin にアクセスします。
下記のような画面が表示されると思います。
スクリーンショット 2021-10-15 8.36.46.png

次に事前に登録して管理者ユーザーのメールアドレスとパスワードを入力してログインします。
ログインが成功し、/adminにリダイレクトされ下記の画面が表示されればOKです。

スクリーンショット 2021-10-15 8.40.59.png

またログイン後にリロードしてもStoreにログインユーザが設定され管理画面から遷移されないことを確認します。
ログアウトボタンを押下し、ログイン画面まで遷移されればOKです。

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7