💡
LaravelとVueでログイン認証実装
Laravel + Vue.js で API 認証を実装する方法
はじめに
この記事では、Laravel と Vue.js を使用して API 認証を実装する方法について解説します。Laravel Sanctum を使用することで、安全で使いやすい API 認証システムを構築していきましょう。
目次
- 環境構築
 - Laravel Sanctum の設定
 - 認証用 API の実装
 - Vue.js 側の実装
 - 認証状態の管理
 - まとめ
 
1. 環境構築
必要なパッケージのインストール
# Laravel側
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
# Vue.js側
npm install axios pinia vue-router
2. Laravel Sanctum の設定
User モデルの設定
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
    // 既存のコード...
}
環境変数の設定
.envファイルに以下の設定を追加します:
SANCTUM_STATEFUL_DOMAINS=localhost:8000,localhost:8081
SESSION_DOMAIN=localhost
3. 認証用 API の実装
認証コントローラーの作成
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
    /**
     * ログイン処理
     */
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);
        $user = User::where('email', $request->email)->first();
        if (!$user || !Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['メールアドレスまたはパスワードが正しくありません。'],
            ]);
        }
        // 既存のトークンを削除し、新しいトークンを発行
        $user->tokens()->delete();
        $token = $user->createToken('auth_token', ['*'], now()->addHours(24));
        return response()->json([
            'token' => $token->plainTextToken,
            'user' => $user,
            'expires_at' => now()->addHours(24)->toDateTimeString(),
        ]);
    }
    /**
     * ログアウト処理
     */
    public function logout(Request $request)
    {
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'ログアウトしました。']);
    }
    /**
     * ユーザー情報取得
     */
    public function me(Request $request)
    {
        return response()->json($request->user());
    }
}
ルートの設定
<?php
use App\Http\Controllers\AuthController;
use Illuminate\Support\Facades\Route;
// 認証なしでアクセス可能
Route::post('/login', [AuthController::class, 'login']);
// 認証が必要なルート
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get('/me', [AuthController::class, 'me']);
});
4. Vue.js 側の実装
認証用ストアの作成
import { defineStore } from "pinia";
import axios from "axios";
const API_URL = "http://localhost:8000/api";
const axiosInstance = axios.create({
  baseURL: API_URL,
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
  },
});
export const useAuthStore = defineStore("auth", {
  state: () => ({
    token: sessionStorage.getItem("token") || null,
    user: JSON.parse(sessionStorage.getItem("user") || "null"),
    expiresAt: sessionStorage.getItem("expiresAt") || null,
  }),
  getters: {
    isAuthenticated: (state) => !!state.token && !!state.user,
    isTokenExpired: (state) => {
      if (!state.expiresAt) return true;
      return new Date(state.expiresAt) < new Date();
    },
  },
  actions: {
    async login(credentials) {
      try {
        const response = await axiosInstance.post("/login", credentials);
        this.token = response.data.token;
        this.user = response.data.user;
        this.expiresAt = response.data.expires_at;
        // セッションストレージに保存
        sessionStorage.setItem("token", this.token);
        sessionStorage.setItem("user", JSON.stringify(this.user));
        sessionStorage.setItem("expiresAt", this.expiresAt);
        this.setAxiosAuthHeader();
        return response;
      } catch (error) {
        return Promise.reject(error);
      }
    },
    async logout() {
      try {
        if (this.token) {
          await axiosInstance.post("/logout");
        }
      } catch (error) {
        console.error("ログアウト中にエラーが発生しました:", error);
      } finally {
        this.clearAuth();
      }
    },
    setAxiosAuthHeader() {
      if (this.token) {
        axiosInstance.defaults.headers.common[
          "Authorization"
        ] = `Bearer ${this.token}`;
      } else {
        delete axiosInstance.defaults.headers.common["Authorization"];
      }
    },
    clearAuth() {
      this.token = null;
      this.user = null;
      this.expiresAt = null;
      sessionStorage.removeItem("token");
      sessionStorage.removeItem("user");
      sessionStorage.removeItem("expiresAt");
      delete axiosInstance.defaults.headers.common["Authorization"];
    },
  },
});
ログインコンポーネントの実装
<template>
  <div class="login-container">
    <div class="login-form">
      <h1>ログイン</h1>
      <div v-if="error" class="error-message">
        {{ error }}
      </div>
      <form @submit.prevent="handleLogin">
        <div class="form-group">
          <label for="email">メールアドレス</label>
          <input
            type="email"
            id="email"
            v-model="form.email"
            required
            autocomplete="username"
          />
        </div>
        <div class="form-group">
          <label for="password">パスワード</label>
          <input
            type="password"
            id="password"
            v-model="form.password"
            required
            autocomplete="current-password"
          />
        </div>
        <button type="submit" :disabled="loading">
          {{ loading ? "ログイン中..." : "ログイン" }}
        </button>
      </form>
    </div>
  </div>
</template>
<script setup>
import { ref } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useAuthStore } from "@/stores/auth";
const router = useRouter();
const route = useRoute();
const authStore = useAuthStore();
const form = ref({
  email: "",
  password: "",
});
const loading = ref(false);
const error = ref("");
const handleLogin = async () => {
  loading.value = true;
  error.value = "";
  try {
    await authStore.login(form.value);
    const redirectPath = route.query.redirect || "/dashboard";
    router.push(redirectPath);
  } catch (err) {
    if (err.response?.data?.message) {
      error.value = err.response.data.message;
    } else if (err.response?.data?.errors) {
      const errorMessages = Object.values(err.response.data.errors);
      error.value = errorMessages.flat().join("\n");
    } else {
      error.value = "ログインに失敗しました。再度お試しください。";
    }
  } finally {
    loading.value = false;
  }
};
</script>
5. 認証状態の管理
認証用ミドルウェア
import { useAuthStore } from "@/stores/auth";
export function authGuard(to, from, next) {
  const authStore = useAuthStore();
  const isAuthenticated = authStore.checkTokenExpiration();
  if (to.meta.requiresAuth && !isAuthenticated) {
    return next({ name: "login", query: { redirect: to.fullPath } });
  } else if (to.meta.requiresGuest && isAuthenticated) {
    return next({ name: "dashboard" });
  }
  return next();
}
Axios インターセプターの設定
import axios from "axios";
import { useAuthStore } from "@/stores/auth";
const axiosInstance = axios.create({
  baseURL: "http://localhost:8000/api",
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
  },
});
// レスポンスインターセプター
axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      const authStore = useAuthStore();
      authStore.clearAuth();
      window.location.href = "/login";
    }
    return Promise.reject(error);
  }
);
// リクエストインターセプター
axiosInstance.interceptors.request.use(
  (config) => {
    const authStore = useAuthStore();
    const token = authStore.token;
    if (token) {
      config.headers["Authorization"] = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);
export default axiosInstance;
6. まとめ
この記事では、Laravel Sanctum を使用した API 認証の実装方法について解説しました。主なポイントは以下の通りです:
- 
環境構築
- Laravel Sanctum のインストール
 - 必要なパッケージのセットアップ
 
 - 
認証システムの実装
- トークンベースの認証
 - セッション管理
 - セキュアな API 通信
 
 - 
フロントエンド実装
- Pinia を使用した状態管理
 - ルーティング制御
 - エラーハンドリング
 
 
セキュリティに関する注意点
- 本番環境では適切な CORS 設定が必要です
 - トークンの有効期限は適切に設定してください
 - セッションストレージの代わりに HttpOnly Cookie の使用を検討してください
 
次のステップ
- パスワードリセット機能の実装
 - メール認証の追加
 - 2 要素認証の実装
 
参考リンク
この記事が皆様の開発のお役に立てれば幸いです。
Discussion