はじめに
本記事は Laravel Passport で OAuth2 を用いた認証機能を作成する手順の一部です。
事前にこちらの記事からご覧ください。
※ 太字の部分が本記事で説明している内容です。
【リソースサーバ 兼 認証サーバ】
1. Laravel Passport のインストール
2. Laravel Passport の初期設定
3. ログイン画面の作成
4. マイページ画面の作成
5. ユーザ情報確認/変更画面の作成
6. ログアウト処理の追加
【クライアントアプリ】
7. トークンのリクエスト処理の追加
8. Laravel Sanctum の初期設定
9. ログインユーザ情報表示画面の作成
10. ログアウト処理の追加
【リソースサーバ 兼 認証サーバ】
11. Laravel Passport のルート登録
12. トークン取消 API の作成
作成手順
5. ユーザ情報確認/変更画面の作成
View の作成
ユーザ情報確認画面として表示する user\Index.vue を新たに作成します。
初期表示時にユーザ情報を取得する API を実行しています。未認証であればログイン画面にリダイレクトします。
<script setup>
import axios from 'axios'
import { useRouter } from 'vue-router'
const router = useRouter()
let user = null
await axios
  .get('/api/user')
  .then((response) => {
    user = response?.data
  })
  .catch((reason) => {
    if (reason?.response?.status === 401) {
      window.location.href = '/login'
    }
  })
</script>
<template>
  <div class="flex justify-center">
    <div class="max-w-md w-full rounded overflow-hidden shadow-lg my-6 mx-4 md:mx-auto">
      <div class="font-bold text-xl px-4 pt-6 pb-2">ユーザ情報確認</div>
      <div class="flex justify-end mx-4 mb-4">
        <RouterLink
          class="text-sm underline underline-offset-4 text-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-300"
          to="/user/edit"
        >
          ユーザ情報変更
        </RouterLink>
      </div>
      <div class="border-y border-gray-100">
        <dl v-if="user !== null" class="divide-y divide-gray-100">
          <div class="px-4 py-6 sm:grid sm:grid-cols-3">
            <dt class="text-sm font-medium leading-6 text-gray-900">ユーザ名</dt>
            <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
              {{ user.name }}
            </dd>
          </div>
          <div class="px-4 py-6 sm:grid sm:grid-cols-3">
            <dt class="text-sm font-medium leading-6 text-gray-900">メールアドレス</dt>
            <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
              {{ user.email }}
            </dd>
          </div>
          <div class="px-4 py-6 sm:grid sm:grid-cols-3">
            <dt class="text-sm font-medium leading-6 text-gray-900">最終ログイン日時</dt>
            <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
              {{ user.last_login_at }}
            </dd>
          </div>
        </dl>
      </div>
      <div class="flex justify-center my-4">
        <button
          type="button"
          class="rounded-md bg-white border-2 px-5 py-2 text-sm shadow-sm hover:bg-gray-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300"
          @click="router.push('/')"
        >
          戻る
        </button>
      </div>
    </div>
  </div>
</template>
ユーザ情報変更画面として表示する user\Edit.vue を新たに作成します。
初期表示時にユーザ情報を取得する API を実行しています。未認証であればログイン画面にリダイレクトします。
<script setup>
import axios from 'axios'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const form = ref({
  name: '',
  email: ''
})
await axios
  .get('/api/user')
  .then((response) => {
    form.value = {
      name: response?.data?.name ?? '',
      email: response?.data?.email ?? ''
    }
  })
  .catch((reason) => {
    if (reason?.response?.status === 401) {
      window.location.href = '/login'
    }
  })
const errors = ref({})
const submit = async () => {
  await axios
    .post('/user', form.value)
    .then((response) => {
      router.push('/user')
    })
    .catch((reason) => {
      if (reason?.response?.status === 422) {
        errors.value = reason.response.data?.errors ?? {}
        console.log(errors.value)
      }
    })
}
</script>
<template>
  <div class="flex justify-center">
    <div class="max-w-md w-full rounded overflow-hidden shadow-lg my-6 mx-4 md:mx-auto">
      <div class="font-bold text-xl px-4 pt-6 pb-2">ユーザ情報確認</div>
      <form @submit.prevent="submit()">
        <div class="px-4 py-2">
          <label for="name" class="text-sm font-medium">ユーザ名</label>
          <div class="mt-2">
            <input
              v-model="form.name"
              id="name"
              name="name"
              type="name"
              placeholder="Test User"
              class="w-full rounded-md border-0 p-2 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 sm:leading-6"
            />
            <div
              v-if="errors.name"
              class="bg-red-100 border border-red-400 text-red-700 px-2 py-1 mt-1 text-sm rounded relative"
              role="alert"
            >
              <div v-for="error in errors.name">{{ error }}</div>
            </div>
          </div>
        </div>
        <div class="px-4 py-2">
          <label for="email" class="text-sm font-medium">メールアドレス</label>
          <div class="mt-2">
            <input
              v-model="form.email"
              id="email"
              name="email"
              type="email"
              placeholder="test@example.com"
              class="w-full rounded-md border-0 p-2 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 sm:leading-6"
            />
            <div
              v-if="errors.email"
              class="bg-red-100 border border-red-400 text-red-700 px-2 py-1 mt-1 text-sm rounded relative"
              role="alert"
            >
              <div v-for="error in errors.email">{{ error }}</div>
            </div>
          </div>
        </div>
        <div class="flex justify-center my-4 space-x-4">
          <button
            type="button"
            class="rounded-md bg-white border-2 px-5 py-2 text-sm shadow-sm hover:bg-gray-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300"
            @click="router.push('/user')"
          >
            キャンセル
          </button>
          <button
            type="submit"
            class="rounded-md bg-indigo-600 px-5 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
          >
            変更
          </button>
        </div>
      </form>
    </div>
  </div>
</template>
ログイン画面からユーザ情報確認画面へ遷移する処理を Index.vue に追加します。
<script setup>
import axios from 'axios'
+ import { useRouter } from 'vue-router'
+ const router = useRouter()
  <button
    type="button"
    class="rounded-md bg-gray-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-600"
+   @click="router.push('/user')"
  >
    ユーザ情報確認
  </button>
Controller の作成
ユーザ情報更新処理を実行する UserController を作成します。ユーザ情報更新処理の内容は以下の通りです。
- 
nameとemailの入力値をチェックする。
- ログインユーザの nameとemailを更新する。
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class UserController extends Controller
{
    public function update(Request $request): JsonResponse
    {
        $inputs = $request->validate([
            'name' => ['required'],
            'email' => ['required', 'email'],
        ]);
        /** @var User ログインユーザ */
        $user = Auth::user();
        $user->fill($inputs)->save();
        return response()->json();
    }
}
ルートの登録
追加した View を表示する ルートを登録します。
Route::middleware(['auth:web'])->group(function () {
    Route::get('/', fn () => view('app'));
+   Route::get('/user/{path?}', fn () => view('app'))->where('path', 'edit');
});
  routes: [
    {
      path: '/',
      name: 'index',
      component: () => import('../views/Index.vue')
+   },
+   {
+     path: '/user',
+     name: 'user.index',
+     component: () => import('../views/user/Index.vue')
+   },
+   {
+     path: '/user/edit',
+     name: 'user.edit',
+     component: () => import('../views/user/Edit.vue')
    }
  ]
追加した Controller にアクセスするルートを登録します。
Route::middleware(['auth:web'])->group(function () {
    Route::get('/', fn () => view('app'));
    Route::get('/user/{path?}', fn () => view('app'))->where('path', 'edit');
+   Route::post('/user', [App\Http\Controllers\UserController::class, 'update']);
});
6. ログアウト処理の追加
Controller の作成
ログアウト処理を AuthController に追加します。
処理後にリダイレクトを行い、デフォルトでログイン画面に遷移します。もし、redirect_uri パラメータの指定があれば、その指定されたルートへリダイレクトします。(後ほど作成するクライアントアプリからリダイレクトしてきた際にパラメータを指定する想定)
+   /**
+    * ログアウト
+    *
+    * @param Request $request
+    * @return RedirectResponse
+    */
+   public function logout(Request $request): RedirectResponse
+   {
+       Auth::logout();
+
+       $request->session()->invalidate();
+       $request->session()->regenerateToken();
+
+       return redirect($request->input('redirect_uri', '/login'));
+   }
}
ルートの登録
追加したログアウト処理を実行するルートを登録します。
Route::middleware(['auth:web'])->group(function () {
    Route::get('/', fn () => view('app'));
    Route::get('/user/{path?}', fn () => view('app'))->where('path', 'edit');
    Route::post('/user', [App\Http\Controllers\UserController::class, 'update']);
});
+ Route::get('/logout', [App\Http\Controllers\AuthController::class, 'logout']);
動作確認
リソースサーバ 兼 認証サーバの画面の作成が一通り完了しました。ここまで正しく作成できているか確認のため、実際に開発用サーバを起動して動作させてみます。以下のコマンドを実行して、http://localhost:8000 にアクセスします。
npm run build
php artisan serve
ログイン後、「ユーザ情報確認」をクリックするとユーザ情報確認画面が表示されます。
 
また、「ユーザ情報変更」のリンクをクリックするとユーザ情報変更画面が表示されます。実際に値を変更して、変更が反映されれば OK です。
 
最後に、マイページ画面まで戻って「ログアウト」をクリックします。認証が解除されてログイン画面が表示されれば OK です。
おわりに
続きはこちらの記事をご覧ください。