\ ポイント最大11倍! /

Next.jsとDjango REST FrameworkでDjoserを使ったJWT認証を構築する完全ガイド

Next.js(フロントエンド)と Django + DRF(バックエンド)を連携させ、ユーザ登録、ログイン、ログアウト、メールアクティベーション、パスワードリセットなどを Djoser でまかなう構成を解説します。

ここでは、JWT 認証(rest_framework_simplejwt)を使うパターンを基本線とします。

全体の流れとしては、バックエンド側で Djoser を設定し、Next.js 側で JWT を取得・保存・更新する仕組みを実装します。

CORS やクッキー管理、トークンリフレッシュ、ログアウトや保護ルートの制御といった部分も含めて確認していきます。

以降では、ファイル構成例も交えつつ「バックエンド設定 → Djoser 設定 → Next.js 側での連携 → 運用上の注意点」まで段階的に解説します。

バックエンド(Django + DRF 側)の準備

プロジェクト作成と環境構築

まずは Django プロジェクトを作成し、仮想環境を構成します。例は次のとおりです。

Bash
python -m venv venv
source venv/bin/activate
pip install django djangorestframework djoser djangorestframework-simplejwt django-cors-headers
django-admin startproject backend .
cd backend
python manage.py startapp accounts

この accounts アプリを認証関連ロジックの拠点とします。

プロジェクト構成例は次のとおりです。

Bash
backend/
  backend/         Django プロジェクトフォルダ
    settings.py
    urls.py
    
  accounts/
    models.py
    serializers.py
    urls.py
    views.py
  manage.py

settings.py の設定

backend/backend/settings.py に以下の設定を加えます。

Python
# settings.py(主要部分)

INSTALLED_APPS = [
    # Django 標準
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    # サードパーティ
    "rest_framework",
    "rest_framework_simplejwt.token_blacklist",
    "djoser",
    "corsheaders",

    # 自作
    "accounts",
]

MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.common.CommonMiddleware",
]

# CORS 設定(開発時の例)
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    # 本番ドメインも追加します
]

# DRF 設定
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
    "DEFAULT_PERMISSION_CLASSES": (
        "rest_framework.permissions.IsAuthenticated",
    ),
}

# Simple JWT 設定
from datetime import timedelta

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
    "ROTATE_REFRESH_TOKENS": True,
    "BLACKLIST_AFTER_ROTATION": True,
    "AUTH_HEADER_TYPES": ("Bearer",),
}

# Djoser 設定
DJOSER = {
    "LOGIN_FIELD": "email",
    "USER_ID_FIELD": "id",
    "USER_CREATE_PASSWORD_RETYPE": True,
    "SERIALIZERS": {
        "user_create": "accounts.serializers.UserCreateSerializer",
        "user": "accounts.serializers.UserSerializer",
        "current_user": "accounts.serializers.UserSerializer",
    },
    "TOKEN_MODEL": None,  # Token 認証を使わない場合は None
    "PASSWORD_RESET_CONFIRM_URL": "auth/reset-password-confirm/?uid={uid}&token={token}",
    "ACTIVATION_URL": "auth/activate/?uid={uid}&token={token}",
    "SEND_ACTIVATION_EMAIL": True,
    "PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND": True,
}

# メールバックエンド(開発時)
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

ROTATE_REFRESH_TOKENS を有効にするとリフレッシュ時にトークンがローテーションされ、BLACKLIST_AFTER_ROTATION により古いリフレッシュトークンを無効化できます。ユーザ登録時に追加情報を扱いたい場合は SERIALIZERS でシリアライザを差し替えます。

accounts/serializers.py(カスタムユーザモデル拡張想定)

email ログインを使いたい場合は AbstractUser を拡張して email 必須のモデルにすることが多いです。シリアライザの例は次のとおりです。

Python
# accounts/serializers.py

from django.contrib.auth import get_user_model
from djoser.serializers import UserCreateSerializer as DjoserUserCreateSerializer
from djoser.serializers import UserSerializer as DjoserUserSerializer

User = get_user_model()

class UserCreateSerializer(DjoserUserCreateSerializer):
    class Meta(DjoserUserCreateSerializer.Meta):
        model = User
        fields = ("id", "email", "password", "first_name", "last_name")

class UserSerializer(DjoserUserSerializer):
    class Meta(DjoserUserSerializer.Meta):
        model = User
        fields = ("id", "email", "first_name", "last_name")

accounts/models.py(カスタムユーザモデルの例)

Python
# accounts/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    # email を識別子にする場合は username の扱いを再設計します
    pass

settings.py に次を追加します。

Python
AUTH_USER_MODEL = "accounts.User"

urls.py に Djoser のエンドポイントを登録

backend/backend/urls.py を次のように記述します。

Python
# backend/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("auth/", include("djoser.urls")),
    path("auth/", include("djoser.urls.jwt")),
]

これで以下の標準エンドポイントが利用できます。

  • /auth/users/(登録・取得・更新)
  • /auth/users/me/(現在のユーザ情報)
  • /auth/users/activation/, /auth/users/resend_activation/
  • /auth/users/reset_password/, /auth/users/reset_password_confirm/
  • /auth/jwt/create/(ログイン・アクセストークン取得)
  • /auth/jwt/refresh/(リフレッシュ)
  • /auth/jwt/verify/(検証)

マイグレーションとスーパーユーザ作成

Bash
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

ここまででバックエンド側は認証機能を提供できる状態になります。

Next.js 側との接続:JWT 管理と API 呼び出し

Next.js 側では、Djoser の JWT エンドポイントで取得したアクセストークンとリフレッシュトークンを管理し、API 呼び出し時に適切なヘッダーを付与します。

以下では App Router(React Server Components)構成を想定した実装例を示します。

HTTP ラッパ(src/lib/api.ts

TypeScript
// src/lib/api.ts

import { cookies } from "next/headers";
import { redirect } from "next/navigation";

const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL; // 例: http://localhost:8000

async function fetchWithAuth(
  input: RequestInfo,
  init?: RequestInit
): Promise<any> {
  const cookieStore = cookies();
  const accessToken = cookieStore.get("accessToken")?.value;

  const headers = new Headers(init?.headers);
  headers.set("Content-Type", "application/json");
  if (accessToken) {
    headers.set("Authorization", `Bearer ${accessToken}`);
  }

  const resp = await fetch(`${BASE_URL}${input}`, {
    credentials: "include",
    ...init,
    headers,
  });

  if (resp.status === 401) {
    // アクセストークン失効時はリフレッシュを試みます
    const refreshResp = await fetch(`${BASE_URL}/auth/jwt/refresh/`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ refresh: cookieStore.get("refreshToken")?.value }),
    });
    if (refreshResp.ok) {
      const data = await refreshResp.json();
      headers.set("Authorization", `Bearer ${data.access}`);
      const retry = await fetch(`${BASE_URL}${input}`, {
        credentials: "include",
        ...init,
        headers,
      });
      return retry.json();
    } else {
      redirect("/login");
    }
  }

  return resp.json();
}

export default fetchWithAuth;

認証ユーティリティ(サーバーアクション)

TypeScript
// src/app/auth/actions.ts

"use server";

import { cookies } from "next/headers";
import { redirect } from "next/navigation";

const BASE = process.env.NEXT_PUBLIC_API_BASE_URL;

export async function loginUser(email: string, password: string) {
  const resp = await fetch(`${BASE}/auth/jwt/create/`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email, password }),
  });
  if (!resp.ok) {
    throw new Error("ログインに失敗しました");
  }
  const data = await resp.json();
  const cookieStore = cookies();
  cookieStore.set("accessToken", data.access, { path: "/", httpOnly: true });
  cookieStore.set("refreshToken", data.refresh, { path: "/", httpOnly: true });
  redirect("/");
}

export function logoutUser() {
  const cookieStore = cookies();
  cookieStore.delete("accessToken");
  cookieStore.delete("refreshToken");
  redirect("/login");
}

ログインページ(クライアントコンポーネント)

TypeScript
// app/login/page.tsx

"use client";

import { useState } from "react";
import { loginUser } from "../auth/actions";

export default function LoginPage() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState<string | null>(null);

  const onSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await loginUser(email, password);
    } catch (err: any) {
      setError(err.message || "ログイン中にエラーが発生しました");
    }
  };

  return (
    <main>
      <h1>ログイン</h1>
      <form onSubmit={onSubmit}>
        <label>
          Email:
          <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
        </label>
        <label>
          パスワード:
          <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
        </label>
        {error && <p style={{ color: "red" }}>{error}</p>}
        <button type="submit">ログイン</button>
      </form>
    </main>
  );
}

プロテクトルートの例(サーバーコンポーネント)

TypeScript
// app/protected/page.tsx

import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import fetchWithAuth from "@/lib/api";

export default async function ProtectedPage() {
  const cookieStore = cookies();
  const accessToken = cookieStore.get("accessToken")?.value;
  if (!accessToken) {
    redirect("/login");
  }

  const user = await fetchWithAuth("/auth/users/me/", { method: "GET" });

  return (
    <main>
      <h1>ようこそ{user.email} さん</h1>
    </main>
  );
}

運用上の注意とカスタマイズ

メールアクティベーション

SEND_ACTIVATION_EMAIL = True により、登録時に確認メールを送信できます。

開発環境ではコンソールバックエンドを使って挙動を確認し、本番環境では SMTP を設定します。

パスワードリセット

パスワードリセットは /auth/users/reset_password/ にメールアドレスを送信し、
メールに含まれる UID・トークンを用いて /auth/users/reset_password_confirm/
新しいパスワードを設定します。

Next.js 側ではこれに対応するフォーム画面を用意します。

トークン無効化とログアウト

リフレッシュトークンのローテーションとブラックリスト化を併用し、トークン再利用を防ぎます。

必要に応じて /auth/jwt/blacklist/ を利用します。

Next.js 側では logoutUser() を呼び出してクッキーを削除し、安全にログアウトします。

クッキー設定

クッキーには httpOnlySecureSameSite 属性を設定します。

これにより、XSSやセッション固定攻撃を防ぐことができます。

CORS と CSRF

django-cors-headers で Next.js のオリジンを明示的に許可します。

JWT 認証を使う場合、通常 CSRF トークンは不要ですが、フォームベースの POST では CSRF トークンを利用する設計も可能です。

Djoser のカスタマイズ

Djoser のデフォルトシリアライザやビューは拡張可能です。

ユーザ登録時に追加フィールドを返したり、ログイン後に独自のレスポンスを返すなど、柔軟な実装ができます。

認証方式の比較

JWT 認証と rest_framework.authtoken のいずれも利用可能です。

ただし、有効期限管理やスケーラビリティの観点からは JWT を推奨します。

まとめ(チェックポイント)

以下が一通り動作していれば、Next.js と DRF をつなぐ安全な JWT 認証基盤が整います。

  • /auth/jwt/create/ でログイントークンを取得できる
  • /auth/jwt/refresh/ でリフレッシュが行える
  • /auth/users/ でユーザ登録ができる
  • /auth/users/me/ でユーザ情報を取得できる
  • メールアクティベーションとパスワードリセットが機能する
  • Next.js 側でトークンの保存・更新・削除が正しく行える
  • 保護ルートへのアクセス制御が機能する
  • CORS・クッキー・(必要なら)CSRF の設定が適切である

今後は、Auth.js(NextAuth.js)との比較や統合、型定義の最適化、テスト観点の追加なども掘り下げていきます。

この記事が気に入ったら
フォローしてね!

シェア・記事の保存はこちら!

この記事を書いた人

CFXLOGのアバター CFXLOG プログラマ

メイン言語はPython。本ブログでは、実務や普段の学習で学んだことをアウトプットしています。
基本情報技術者試験合格者。

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)