昨日 14:56
バックエンド laravel フロントエンドNextJS クッキーを使って認証 まずはLaravel11 sctumuで認証構築 Postmanで確認したい
ChatGPT
はい。
Laravel 11をバックエンド、Next.jsをフロントエンドにして、Sanctumの「Cookie + Session」方式で認証する構成で進めるのがよいです。
まずは Next.jsはまだ触らず、Laravel 11 + Sanctumを構築してPostmanで完全に確認しましょう。
Laravel公式でも、Next.jsのようなSPAにはSanctumのSPA認証を使い、APIトークンではなくLaravelのセッションCookieを使う方式が推奨されています。
今回の構成はこうします。
Next.js
│
│ Cookie
▼
Laravel 11
│
├─ Sanctum
├─ Session
├─ CSRF
└─ MySQL
最終的には、
POST /sanctum/csrf-cookie
↓
POST /api/login
↓
LaravelがセッションCookie発行
↓
GET /api/user
↓
ログインユーザー取得
という流れになります。
- Laravel 11プロジェクト
まだ作っていない場合、
composer create-project laravel/laravel laravel-backend “^11.0”
cd laravel-backend
Laravel 11ではSanctumを
php artisan install:api
で導入できます。公式ドキュメントでもこの方法が案内されています。
実行します。
php artisan install:api
途中で質問が出たら、基本的にはデフォルトで進めてください。
その後、
php artisan migrate
- データベース設定
.env
例えばMySQLなら、
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=next_laravel
DB_USERNAME=root
DB_PASSWORD=
データベースを作成します。
CREATE DATABASE next_laravel;
そして、
php artisan migrate
- Userモデル確認
app/Models/User.php
Laravel 11 + Sanctumなら、最終的にはこのような形にしておけばOKです。
<?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;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}
ただし、今回のCookie認証ではSanctumのAPIトークンを発行するわけではありません。
ここは重要です。
❌ localStorageにSanctum tokenを保存
❌ Authorization: Bearer xxx
⭕ Laravel Session Cookie
⭕ XSRF-TOKEN
SanctumのSPA認証はCookieベースのセッション認証です。
- Sanctumのstateful設定
Laravel 11ではここが非常に重要です。
bootstrap/app.php
確認してください。
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(DIR))
->withRouting(
web: DIR.’/../routes/web.php’,
api: DIR.’/../routes/api.php’,
commands: DIR.’/../routes/console.php’,
health: ‘/up’,
)
->withMiddleware(function (Middleware $middleware) {
$middleware->statefulApi();
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
特に、
$middleware->statefulApi();
が重要です。
Laravel公式でも、SPAからのCookie認証を有効にするために statefulApi() を使用するよう説明されています。
- Sanctumのstateful domains
.envに、
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
を設定します。
ただし、今回Postmanから確認する段階ではlocalhost:8000からアクセスするので、まずはPostman確認を優先した設定にしても構いません。
最終的にNext.jsを
http://localhost:3000
Laravelを
http://localhost:8000
で動かすなら、
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
としておきます。
ポート番号まで含める必要があります。
- CORS設定
Laravel 11では config/cors.php が最初から存在しない場合があります。
その場合、
php artisan config:publish cors
を実行します。
Laravel公式もSPAでCORSをカスタマイズする場合、このコマンドを使用するよう案内しています。
config/cors.php
<?php
return [
'paths' => [
'api/*',
'sanctum/csrf-cookie',
],
'allowed_methods' => ['*'],
'allowed_origins' => [
'http://localhost:3000',
],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
特に、
‘supports_credentials’ => true,
が重要です。
- Session設定
.env
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_DOMAIN=null
SESSION_SECURE_COOKIE=false
SESSION_SAME_SITE=lax
ローカル環境ではこれで始めましょう。
まだ
SESSION_DOMAIN=.example.com
などにはしません。
- Login APIを作る
今回はLaravel側にシンプルなAPIを作ります。
まずController。
php artisan make:controller AuthController
app/Http/Controllers/AuthController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function login(Request $request)
{
$credentials = $request->validate([
‘email’ => [‘required’, ‘email’],
‘password’ => [‘required’],
]);
if (!Auth::attempt($credentials)) {
throw ValidationException::withMessages([
'email' => ['メールアドレスまたはパスワードが正しくありません。'],
]);
}
$request->session()->regenerate();
return response()->json([
'message' => 'ログインしました',
'user' => $request->user(),
]);
}
public function user(Request $request)
{
return response()->json(
$request->user()
);
- APIルート
routes/api.php
<?php
use App\Http\Controllers\AuthController;
use Illuminate\Support\Facades\Route;
Route::post(‘/login’, [AuthController::class, ‘login’]);
Route::middleware(‘auth:sanctum’)->group(function () {
Route::get('/user', [AuthController::class, 'user']);
Route::post('/logout', [AuthController::class, 'logout']);
});
これで、
POST /api/login
GET /api/user
POST /api/logout
ができます。
- テストユーザーを作る
まず、
php artisan tinker
そして、
\App\Models\User::create([
‘name’ => ‘Test User’,
‘email’ => ‘test@example.com’,
‘password’ => ‘password123’,
]);
終了。
exit
- Laravel起動
php artisan serve
通常、
http://127.0.0.1:8000
または
http://localhost:8000
になります。
- ここからPostman
ここが今回の重要部分です。
Step 1 CSRF Cookie取得
Postmanで、
GET
http://localhost:8000/sanctum/csrf-cookie
を実行。
Bodyはありません。
Headerに、
Accept: application/json
を入れておきます。
成功するとレスポンスはほぼ空ですが、Cookiesを見ると
XSRF-TOKEN
laravel_session
が設定されます。
Laravel公式でも、SPA認証では最初に /sanctum/csrf-cookie を呼び、XSRF-TOKEN Cookieを取得してからログインする流れになっています。
- PostmanでCookieを確認
PostmanのURL欄付近にある
Cookies
をクリックしてください。
例えば、
localhost
に、
XSRF-TOKEN
laravel_session
が存在することを確認します。
ここまで来ればかなり重要なところまで成功しています。
- Login
次に、
POST
http://localhost:8000/api/login
Header:
Accept: application/json
Content-Type: application/json
origin:http://localhost:3000
Body → raw → JSON
{
“email”: “test@example.com”,
“password”: “password123”
}
ただし、ここで一つ注意があります。
CSRF対策のため、X-XSRF-TOKENも送る必要があります。
Postmanでは、取得した XSRF-TOKEN Cookieの値をURLデコードして、
X-XSRF-TOKEN
Headerに設定します。
例えば、
X-XSRF-TOKEN:
です。
ブラウザ+Axiosの場合はAxiosが自動的にこの処理を行えますが、Postmanでは自分で確認する必要があります。Laravel公式もこの仕組みを説明しています。
- Login成功
成功すれば、
{
“message”: “ログインしました”,
“user”: {
“id”: 1,
“name”: “Test User”,
“email”: “test@example.com”
}
}
のようになります。
さらにPostmanのCookieを見ると、
laravel_session
が更新されているはずです。
この laravel_session が今回の認証の本体です。
- 認証確認
次に、
GET
http://localhost:8000/api/user
Header:
Accept: application/json
Cookieは先ほどの
laravel_session
を使います。
成功すれば、
{
“id”: 1,
“name”: “Test User”,
“email”: “test@example.com”,
…
}
が返ります。
これが、
「CookieによるLaravel Sanctum認証が成功した」
という確認になります。
Laravel公式の保護されたAPIも、auth:sanctum を付けた /user に対して $request->user() を返す形になっています。
- Logout
最後に、
POST
http://localhost:8000/api/logout
を実行。
成功:
{
“message”: “ログアウトしました”
}
その後もう一度、
GET
http://localhost:8000/api/user
すると、
401 Unauthorized
になることを確認します。
今回の確認ポイント
Postmanでは、この順番でテストしてください。
① GET
/sanctum/csrf-cookie
↓
XSRF-TOKEN
laravel_session
↓
② POST
/api/login
↓
Cookieによるログイン
↓
③ GET
/api/user
↓
ユーザー情報取得
↓
④ POST
/api/logout
↓
⑤ GET
/api/user
↓
401 Unauthorized
ここまで成功したら、Laravel側のCookie認証は完成です。
次の段階
推奨構成
Next.js側をこのようにします。
src/
├── app/
│ ├── login/
│ │ └── page.tsx
│ ├── management/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── providers.tsx
│
├── lib/
│ └── axios.ts
│
├── types/
│ └── User.ts
│
├── api/
│ └── AuthAPI.ts
│
├── queries/
│ └── AuthQuery.ts
│
└── hooks/
└── AuthContext.tsx
役割は、
AuthAPI.ts
↓
Laravel APIとの通信
AuthQuery.ts
↓
useUser
useLogin
useLogout
AuthContext.tsx
↓
必要なら認証状態を提供
TanStack Query
↓
ユーザー情報をキャッシュ
です。
- TanStack Queryをインストール
Next.jsプロジェクトで、
npm install @tanstack/react-query axios
を実行します。
以前の
import { useQuery } from “react-query”
ではなく、現在は
import { useQuery } from “@tanstack/react-query”
を使います。
- User型
src/types/User.ts
export type User = {
id: number
name: string
email: string
email_verified_at: string | null
created_at: string
updated_at: string
}
3. Axios
ここがCookie認証では重要です。
src/lib/axios.ts
import axios from “axios”
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
withCredentials: true,
withXSRFToken: true,
headers: {
Accept: “application/json”,
},
})
export default api
.env.local
NEXT_PUBLIC_API_URL=http://localhost:8000
これで、
api.get(“/api/user”)
と書けば、
http://localhost:8000/api/user
にアクセスできます。
- AuthAPI
元々のコードをかなりそのまま使えます。
ただし、Sanctumの場合はログイン前にCSRF Cookieを取得する必要があります。
src/api/AuthAPI.ts
import api from “@/lib/axios”
import { User } from “@/types/User”
type LoginParams = {
email: string
password: string
}
export const getCsrfCookie = async () => {
await api.get(“/sanctum/csrf-cookie”)
}
export const getUser = async (): Promise => {
const { data } = await api.get(“/api/user”)
return data
}
export const login = async ({
email,
password,
}: LoginParams): Promise => {
await getCsrfCookie()
const { data } = await api.post<{
message: string
user: User
}>(“/api/login”, {
email,
password,
})
return data.user
}
export const logout = async () => {
const { data } = await api.post(“/api/logout”)
return data
}
ここが非常に重要です。
await getCsrfCookie()
↓
api.post(“/api/login”)
という順番です。
- QueryClient
Next.js App Routerなので、Providerを作ります。
src/app/providers.tsx
“use client”
import {
QueryClient,
QueryClientProvider,
} from “@tanstack/react-query”
import { useState } from “react”
export default function Providers({
children,
}: {
children: React.ReactNode
}) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: false,
},
},
})
)
return (
{children}
)
}
6. layout.tsx
src/app/layout.tsx
import Providers from “./providers”
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
{children}
)
}
これでアプリ全体からTanStack Queryが使えます。
- AuthQuery
ここが、以前のReact Query版の中心部分に相当します。
src/queries/AuthQuery.ts
“use client”
import {
useMutation,
useQuery,
useQueryClient,
} from “@tanstack/react-query”
import {
getUser,
login,
logout,
} from “@/api/AuthAPI”
type LoginParams = {
email: string
password: string
}
まずUser。
export const useUser = () => {
return useQuery({
queryKey: [“user”],
queryFn: getUser,
retry: false,
staleTime: 5 * 60 * 1000,
})
}
これだけで、
const { data: user, isLoading, isError } = useUser()
とできます。
- Login
export const useLogin = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (params: LoginParams) =>
login(params),
onSuccess: (user) => {
queryClient.setQueryData(
["user"],
user
)
},
})
}
これなら、
Login成功
↓
user情報
↓
[“user”] Queryに保存
となります。
- Logout
export const useLogout = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: logout,
onSuccess: () => {
queryClient.removeQueries({
queryKey: ["user"],
})
},
})
}
これで、
Logout
↓
Laravel Session破棄
↓
[“user”]削除
↓
未ログイン状態
になります。
- AuthContextは必須ではありません
ここは以前のReact版と少し考え方を変えた方がいいです。
以前は、
const [isAuth, setIsAuth] = useState(false)
としていました。
しかしCookie認証 + TanStack Queryなら、
isAuth
を別途stateとして管理する必要がありません。
例えば、
const { data: user } = useUser()
で、
user !== undefined
ならログイン、
user === undefined
なら未ログイン、
と判断できます。
より正確には、
const {
data: user,
isLoading,
isError,
} = useUser()
として、
isLoading
↓
認証確認中
user
↓
ログイン
isError
↓
未ログイン
とします。
- それでもAuthContextを使いたい場合
もちろんできます。
例えば、
src/hooks/AuthContext.tsx
“use client”
import {
createContext,
useContext,
ReactNode,
} from “react”
import { useUser } from “@/queries/AuthQuery”
type AuthContextType = {
user: ReturnType[“data”]
isLoading: boolean
isAuthenticated: boolean
}
const AuthContext =
createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({
children,
}: {
children: ReactNode
}) {
const {
data: user,
isLoading,
} = useUser()
return (
<AuthContext.Provider
value={{
user,
isLoading,
isAuthenticated: !!user,
}}
>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error(
“useAuth must be used within AuthProvider”
)
}
return context
}
ただ、私は最初はAuthContextなしで進めることをおすすめします。
TanStack Queryが認証状態を管理してくれるからです。
- Loginページ
例えば、
src/app/login/page.tsx
“use client”
import { FormEvent, useState } from “react”
import { useRouter } from “next/navigation”
import { useLogin } from “@/queries/AuthQuery”
export default function LoginPage() {
const router = useRouter()
const loginMutation = useLogin()
const [email, setEmail] = useState(“”)
const [password, setPassword] = useState(“”)
const handleSubmit = async (
e: FormEvent
) => {
e.preventDefault()
try {
await loginMutation.mutateAsync({
email,
password,
})
router.push("/management")
} catch (error) {
console.error(error)
}
}
return (
<h1>ログイン</h1>
<form onSubmit={handleSubmit}>
<div>
<label>
メールアドレス
</label>
<input
type="email"
value={email}
onChange={(e) =>
setEmail(e.target.value)
}
/>
</div>
<div>
<label>
パスワード
</label>
<input
type="password"
value={password}
onChange={(e) =>
setPassword(e.target.value)
}
/>
</div>
<button
type="submit"
disabled={loginMutation.isPending}
>
{loginMutation.isPending
? "ログイン中..."
: "ログイン"}
</button>
</form>
{loginMutation.isError && (
<p>
ログインに失敗しました
</p>
)}
</div>
)
}
13. 認証が必要なページ
例えば、
src/app/management/page.tsx
“use client”
import { useRouter } from “next/navigation”
import {
useLogout,
useUser,
} from “@/queries/AuthQuery”
export default function ManagementPage() {
const router = useRouter()
const {
data: user,
isLoading,
isError,
} = useUser()
const logoutMutation = useLogout()
if (isLoading) {
return
認証確認中…
}
if (isError || !user) {
router.replace("/login")
return null
}
const handleLogout = async () => {
try {
await logoutMutation.mutateAsync()
router.replace("/login")
} catch (error) {
console.error(error)
}
}
return (
<h1>管理画面</h1>
<p>
ようこそ、{user.name}さん
</p>
<p>
{user.email}
</p>
<button
onClick={handleLogout}
disabled={logoutMutation.isPending}
>
{logoutMutation.isPending
? "ログアウト中..."
: "ログアウト"}
</button>
</div>
)
}
14. ここで重要なこと
今回の構成では、リロードしてもログイン状態が維持されます。
例えば、
/login
↓
ログイン
↓
/management
↓
laravel_session Cookie
↓
ブラウザがCookie保持
その後、
F5
しても、
useUser()
↓
GET /api/user
↓
Laravel
↓
laravel_session
↓
User取得
となるので、
ログイン状態維持
できます。
localStorageは必要ありません。
今回の構成は以前のReact版よりシンプルです
以前:
AuthContext
↓
isAuth state
↓
React Query
↓
API
今回:
TanStack Query
↓
GET /api/user
↓
Laravel Session Cookie
↓
User
そして、
useUser()
が実質的に認証状態の中心になります。
ただし、1点だけ修正したいです
先ほどLaravel側について、
Route::middleware(‘web’)->group(…)
を追加しましたが、最終構成ではいったん元の api.php に戻すことをおすすめします。
Route::post(‘/login’, [AuthController::class, ‘login’]);
Route::middleware(‘auth:sanctum’)->group(function () {
Route::get(‘/user’, [AuthController::class, ‘user’]);
Route::post(‘/logout’, [AuthController::class, ‘logout’]);
});
そして、
$middleware->statefulApi();
を維持します。
その上でNext.js + Axios側を正しく設定します。
次はこの構成を実際に動かすために、AuthAPI.ts → AuthQuery.ts → providers.tsx → login/page.tsx → management/page.tsx を、あなたの現在のNext.js 15プロジェクトの構成に合わせて完成版として組むのがよいです。
はい。「PCでは画面幅の約50%、スマホでは100%」なら、Tailwindでは w-full md:w-1/2 がシンプルです。
現在の、
を、
に変更してください。
ただし、PCでも大画面になると50%では広すぎる場合があるので、管理画面ならこちらがおすすめです。
動作 スマホ ┌────────────────────┐ │ │ │ コンテンツ │ │ │ └────────────────────┘ 100%
PC
┌──────────────────────────────────────────────┐
│ │
│ ┌────────────────────────┐ │
│ │ │ │
│ │ コンテンツ │ │
│ │ │ │
│ └────────────────────────┘ │
│ │
└──────────────────────────────────────────────┘
約50%
md: は 768px以上で適用されます。
もし「タブレットまでは100%、PC(1024px以上)から50%」にしたいなら、
の方が適しています。
今回の管理画面なら、私は lg:w-1/2 をおすすめします。