NextJS Laravel 環境構築

昨日 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
↓
ログインユーザー取得

という流れになります。

  1. 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

  1. データベース設定

.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

  1. 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ベースのセッション認証です。

  1. 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() を使用するよう説明されています。

  1. 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

としておきます。

ポート番号まで含める必要があります。

  1. 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,

が重要です。

  1. Session設定

.env

SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_DOMAIN=null
SESSION_SECURE_COOKIE=false
SESSION_SAME_SITE=lax

ローカル環境ではこれで始めましょう。

まだ

SESSION_DOMAIN=.example.com

などにはしません。

  1. 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()
    );
  1. 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

ができます。

  1. テストユーザーを作る

まず、

php artisan tinker

そして、

\App\Models\User::create([
‘name’ => ‘Test User’,
‘email’ => ‘test@example.com’,
‘password’ => ‘password123’,
]);

終了。

exit

  1. Laravel起動
    php artisan serve

通常、

http://127.0.0.1:8000

または

http://localhost:8000

になります。

  1. ここから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を取得してからログインする流れになっています。

  1. PostmanでCookieを確認

PostmanのURL欄付近にある

Cookies

をクリックしてください。

例えば、

localhost

に、

XSRF-TOKEN
laravel_session

が存在することを確認します。

ここまで来ればかなり重要なところまで成功しています。

  1. 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公式もこの仕組みを説明しています。

  1. Login成功

成功すれば、

{
“message”: “ログインしました”,
“user”: {
“id”: 1,
“name”: “Test User”,
“email”: “test@example.com”
}
}

のようになります。

さらにPostmanのCookieを見ると、

laravel_session

が更新されているはずです。

この laravel_session が今回の認証の本体です。

  1. 認証確認

次に、

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() を返す形になっています。

  1. 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
↓
ユーザー情報をキャッシュ

です。

  1. TanStack Queryをインストール

Next.jsプロジェクトで、

npm install @tanstack/react-query axios

を実行します。

以前の

import { useQuery } from “react-query”

ではなく、現在は

import { useQuery } from “@tanstack/react-query”

を使います。

  1. 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

にアクセスできます。

  1. 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”)

という順番です。

  1. 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が使えます。

  1. 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()

とできます。

  1. Login
    export const useLogin = () => {

const queryClient = useQueryClient()

return useMutation({
mutationFn: (params: LoginParams) =>
login(params),

onSuccess: (user) => {

  queryClient.setQueryData(
    ["user"],
    user
  )
},

})
}

これなら、

Login成功
↓
user情報
↓
[“user”] Queryに保存

となります。

  1. Logout
    export const useLogout = () => {

const queryClient = useQueryClient()

return useMutation({

mutationFn: logout,

onSuccess: () => {

  queryClient.removeQueries({
    queryKey: ["user"],
  })

},

})
}

これで、

Logout
↓
Laravel Session破棄
↓
[“user”]削除
↓
未ログイン状態

になります。

  1. 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
↓
未ログイン

とします。

  1. それでも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が認証状態を管理してくれるからです。

  1. 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 をおすすめします。

コメント

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です