DELOGs
JWTって何? Next.js での認証方式とトークンの仕組みを徹底解説(超入門)

JWTって何? Next.js での認証方式とトークンの仕組みを徹底解説(超入門)

JWTについて、Next.jsでのログイン認証に使えるトークンの仕組みと活用方法を初心者向けに丁寧に解説

初回公開日

最終更新日

はじめに:ログイン機能は Web アプリの「玄関口」

Web アプリケーションを作るとき、まず実装したくなるのが「ログイン機能」です。ログインとは、ユーザーを識別して、その人専用の画面や機能を提供する仕組みです。たとえば:
  • ログイン中のユーザーだけが使える管理画面
  • 自分専用の設定やデータ
  • 編集や削除などの操作権限の制御
これらを実現するためには、「ユーザーが正しくログインしているか?」を判定する仕組みが必要になります。この仕組みを「認証(Authentication)」と呼びます。
では、Next.js で Web アプリを構築する場合、どのような認証の選択肢があるのでしょうか? それぞれの違いや特徴を理解した上で、今回はその中でも「JWT(JSON Web Token)という手法にフォーカスして解説していきます。

Next.js で使える認証の方法:代表的な 3 つのアプローチ

① NextAuth.js を使った認証(認証ライブラリ)

NextAuth.js は、Next.js 専用に設計されたオープンソースの認証ライブラリです。

🟢 特徴:

  • Google, GitHub, Twitter などの OAuth ログインを簡単に実装できる
  • メールリンク認証、認証コード、JWT など多様な方式に対応
  • 認証状態をセッションとして管理(内部的に JWT 利用も可能)

🛠 使いどころ:

  • SNS 連携ログインを実装したいとき
  • ログイン機能にあまり時間をかけたくないとき
  • シンプルな UI・UX で良いとき

⚠ 注意点:

  • カスタマイズには中級以上のスキルが必要
  • 自作のデータベースと連携させたい場合、schema 定義が必要
  • 複数ドメイン/API 間の連携にはやや不向き
DELOGs では NextAuth.js については別記事で詳しく解説予定です。

② セッションベースの認証(クッキーを使ったログイン)

こちらは、サーバーがユーザーのログイン状態を保存する方法です。 ログイン時に「セッション ID」を発行して、クライアント側(ブラウザ)に Cookie として保存。リクエストのたびにその ID を使って、ログイン状態をチェックします。

🔧 具体的な仕組み:

  • ログイン成功時、サーバーが「セッション ID」を生成
  • セッション ID とユーザー情報をサーバーのメモリや Redis などに保存
  • クライアントに Set-Cookie ヘッダでセッション ID を送信
  • 次回以降のリクエストには Cookie が含まれ、それをサーバー側で検証

🟢 特徴:

  • セキュリティが高い(サーバー管理)
  • 構築が比較的簡単
  • サーバーとクライアントが密に連携する構成では便利

🔴 弱点:

  • スケーラビリティに欠ける(セッションをサーバーに保存)
  • サーバーが増えるとセッション管理が煩雑に
  • API 連携や SPA 構成にはやや不向き

③ JWT(JSON Web Token)認証

そして本記事の主役、JWTです。 JWT とは、ログイン情報を文字列としてトークン化し、クライアントに保持させることで、サーバーは「状態を覚えておかなくていい」=ステートレスな認証手法です。

🧾 JWT とは?

JWT(JSON Web Token)は、次の 3 つのパーツで構成された文字列です:
zsh
1<Header>.<Payload>.<Signature>
例えば、次のような見た目のトークンになります:
zsh
1eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. 2eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. 3SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  • Header: トークンの種別と署名アルゴリズム(例:HS256)
  • Payload: ユーザー情報など(例:ユーザー ID、名前、有効期限など)
  • Signature: 秘密鍵を使った署名(改ざん防止)

🔐 セキュリティの原理:

トークンに署名を施してあるため、内容が改ざんされていないかをサーバーが検証できる

🔍 中身は見えるが改ざんは不可:

  • Payload は Base64 でエンコードされており、誰でもデコードして中身を見られる(暗号化ではない)
  • だからこそ、パスワードや個人情報など機密データは絶対に含めない

🟢 長所:

  • ステートレス(サーバーに情報を保存しなくて良い)
  • 複数サービス間で共通認証ができる(SSO)
  • API 設計と相性が良い(SPA やスマホアプリにも)

🔴 注意点:

  • トークンの無効化が難しい(発行後は基本的に有効期限まで使われる)
  • セキュリティは「署名鍵」に依存(秘密鍵漏洩=大惨事)

JWT の構造と中身を徹底解剖

JWT(JSON Web Token)は、以下の 3 つの構成要素から成り立っています。それぞれの役割や実際の内容について、詳しく解説します。

① Header(ヘッダー)

ヘッダーは、トークンのタイプと、署名に使用するアルゴリズムを示します。
例:
json
1{ 2 "alg": "HS256", 3 "typ": "JWT" 4}
  • alg: 使用される署名アルゴリズム(例:HS256、RS256 など)
  • typ: トークンのタイプ。JWT の場合は常に "JWT"
この部分は小さな JSON オブジェクトであり、Base64URL エンコードされてトークンの先頭部分になります。

② Payload(ペイロード)

ペイロードは、実際のデータ(クレーム:claims)が含まれる部分です。 クレームは、トークンの意味を定義し、ユーザーに関する情報やトークンの属性を伝えます。

🔹 よく使われる標準クレーム(Registered Claims)

フィールド意味
subトークンの主体(Subject)ユーザー ID など(例:"1234567890")
name表示名などのユーザー情報"John Doe"
iat発行日時(Issued At)UNIX タイムスタンプ(例:1620000000)
exp有効期限(Expiration)UNIX タイムで指定(例:1620003600)
aud対象ユーザー(Audience)"https://yourapp.com"
iss発行者(Issuer)"your-backend-server"
これらは全て任意であり、用途に応じて追加または省略できます。
例:
json
1{ 2 "sub": "user_123", 3 "name": "Alice", 4 "role": "admin", 5 "iat": 1718773561, 6 "exp": 1718777161 7}
このようなペイロードは、エンコードされているとはいえ暗号化されていないため、機密情報(パスワードやクレジットカード番号など)を含めてはいけません。

③ Signature(署名)

ヘッダーとペイロードを . で連結した文字列を、秘密鍵を使って署名します。
署名に使われる関数(例:HS256 の場合):
zsh
1HMACSHA256( 2 base64UrlEncode(header) + "." + base64UrlEncode(payload), 3 secretKey 4)
サーバーはこの署名を検証することで、トークンが改ざんされていないことを確認できます。
つまり JWT は、
  • 中身(Payload)は誰でも見られる(Base64URL エンコードのみ)
  • だが、署名により改ざんは防げる という仕組みで成り立っています。
この構造が、ステートレスかつ信頼性のある認証を可能にしているのです。

JWT のセキュリティ設計:安全に使うためのベストプラクティス

JWT は便利で軽量な認証方式ですが、その性質上、セキュリティ対策を怠ると重大なリスクを招くことがあります。ここでは、初心者が特に意識すべき設計上の注意点と、具体的な対策方法を紹介します。

1. 有効期限(exp)は必須

トークンは基本的に一度発行したら失効するまで有効です。 そのため、exp(expiration time)は必ず設定しましょう

💡 ポイント:

  • アクセストークンの有効期限は「15 分〜1 時間」程度が目安
  • 長くしすぎると、漏洩時の被害が拡大する

2. 保存先の選択(Cookie vs LocalStorage)

JWT の保存場所には 2 つの選択肢があります。
保存先特徴セキュリティ面の懸念
localStorage手軽・読み書き自由XSS 攻撃に弱い(JavaScript で盗まれる)
HttpOnly CookieJavaScript からアクセス不可CSRF 攻撃に注意が必要

✅ 推奨:HttpOnly Cookie + SameSite=Strict + Secure(HTTPS)

3. トークンの失効が難しい問題への対策

JWT はステートレスな設計のため、サーバー側で「このトークンは無効です」と判定するのが困難です。

🔄 対策:リフレッシュトークン方式を導入

  • 短命なアクセストークン(15 分)+ 長命なリフレッシュトークン(数日)
  • リフレッシュ時に再検証(DB などで revoked token 管理)

4. HTTPS を必ず使う

HTTP 通信では JWT(プレーンな文字列)が丸見えになります。 絶対に HTTPS を使用し、トークンを傍受されないようにしましょう。

5. 最小限のクレームだけを含める

トークンサイズが大きいと通信負荷が上がるだけでなく、情報漏洩リスクも増えます。

🛑 NG 例:

json
1{ 2 "sub": "user_1", 3 "password": "plaintext_password", // ✖ 入れちゃダメ 4 "email": "example@example.com", // ✖ 過剰情報 5 "name": "Taro" // ○ これはOK 6}

Next.js × JWT の実装パターン:基本のログイン API からトークン検証まで

ここからは、Next.js で JWT 認証を導入する際の最小構成の実装例を紹介します。実際に別の記事で実装する予定です。ここでは流れだけさらりと示します。

前提環境

  • Next.js App Router 構成
  • API Routes(app/api/...)でログイン処理を実装
  • Cookie によるトークン保存
  • .envに秘密鍵(JWT_SECRET)を設定済み

① ログイン API の実装:トークン発行と Cookie 保存

app/api/login/route.ts:
ts
1import { NextResponse } from "next/server"; 2import { sign } from "jsonwebtoken"; 3 4const JWT_SECRET = process.env.JWT_SECRET!; 5 6export async function POST(req: Request) { 7 const { email, password } = await req.json(); 8 9 // 仮のユーザーデータ(実際はDBなどで検証) 10 if (email !== "demo@example.com" || password !== "pass1234") { 11 return NextResponse.json({ error: "Invalid credentials" }, { status: 401 }); 12 } 13 14 const token = sign({ sub: "user_1", name: "Demo User" }, JWT_SECRET, { 15 expiresIn: "1h", 16 }); 17 18 const res = NextResponse.json({ success: true }); 19 res.cookies.set({ 20 name: "token", 21 value: token, 22 httpOnly: true, 23 secure: true, 24 sameSite: "strict", 25 path: "/", 26 maxAge: 60 * 60, // 1時間 27 }); 28 return res; 29}
  • パッケージを使用して JWT を発行
  • トークンはHttpOnlyな Cookie に保存
  • secure: trueにより HTTPS 環境のみで送信される

② 認証済みかどうかを判定するミドルウェア処理

ts
1// lib/auth.ts 2import { verify } from "jsonwebtoken"; 3 4const JWT_SECRET = process.env.JWT_SECRET!; 5 6export function verifyToken(token: string) { 7 try { 8 return verify(token, JWT_SECRET); 9 } catch (err) { 10 return null; 11 } 12}
この関数は、トークンの整合性を検証し、改ざんされていない場合はペイロードを返します。

③ 保護された API ルートの実装(認証ガード)

ts
1// app/api/protected/route.ts 2import { NextRequest, NextResponse } from "next/server"; 3import { verifyToken } from "@/lib/auth"; 4 5export async function GET(req: NextRequest) { 6 const token = req.cookies.get("token")?.value; 7 const payload = token && verifyToken(token); 8 9 if (!payload) { 10 return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 11 } 12 13 return NextResponse.json({ message: "You are authorized", user: payload }); 14}
このルートでは:
  • Cookie からトークンを取得
  • 検証に失敗したら 401 Unauthorized を返却
  • 成功したらユーザー情報を返す

④ ログアウト処理(トークンの削除)

ts
1// app/api/logout/route.ts 2import { NextResponse } from "next/server"; 3 4export async function POST() { 5 const res = NextResponse.json({ success: true }); 6 res.cookies.set("token", "", { 7 maxAge: 0, 8 path: "/", 9 }); 10 return res; 11}
このようにして、JWT を使ったログイン → 保護ルート → ログアウトの流れを最小構成で構築できます。
次は、これを拡張してRBAC(ロールベースのアクセス制御)ユーザー管理 UIへの統合を目指していきます。

RBAC(ロールベースのアクセス制御)の導入

RBAC(Role-Based Access Control)とは、ユーザーに「ロール(役割)」を与え、そのロールに基づいてアクセス制限を行う仕組みです。
たとえば:
  • 管理者(admin)はユーザー一覧や設定変更ができる
  • 一般ユーザー(user)は自身のデータしか閲覧できない

トークンにロール情報を含める

トークンの Payload にroleを含めます:
ts
1const token = sign( 2 { 3 sub: "user_1", 4 name: "Demo User", 5 role: "admin", 6 }, 7 JWT_SECRET, 8 { expiresIn: "1h" } 9);

API 側でロールをチェック

ts
1if (payload.role !== "admin") { 2 return NextResponse.json({ error: "Forbidden" }, { status: 403 }); 3}

管理者専用 API の例

ts
1// app/api/admin/users/route.ts 2export async function GET(req: NextRequest) { 3 const token = req.cookies.get("token")?.value; 4 const payload = token && verifyToken(token); 5 6 if (!payload || payload.role !== "admin") { 7 return NextResponse.json({ error: "Forbidden" }, { status: 403 }); 8 } 9 10 const users = await getAllUsers(); // 管理者だけが呼べる 11 return NextResponse.json(users); 12}

ユーザー管理 UI への統合(クライアント側)

API が整備されたら、次は**フロントエンド側(UI)**でユーザーの状態に応じた表示を行います。

ログイン中ユーザーの情報を取得

ts
1"use client"; 2import { useEffect, useState } from "react"; 3 4export default function Dashboard() { 5 const [user, setUser] = useState(null); 6 7 useEffect(() => { 8 fetch("/api/protected") 9 .then((res) => res.json()) 10 .then((data) => setUser(data.user)); 11 }, []); 12 13 if (!user) return <p>読み込み中...</p>; 14 15 return ( 16 <div> 17 <h1>こんにちは、{user.name}さん</h1> 18 {user.role === "admin" && ( 19 <a href="/admin/users" className="text-blue-500 underline"> 20 ユーザー管理画面へ 21 </a> 22 )} 23 </div> 24 ); 25}
このようにして、バックエンドのトークン検証とロール確認 → クライアント側の表示制御まで連携することで、柔軟なユーザーアクセス管理を実現できます。
次は、認証のよくあるミスや Q&A を通じてさらに理解を深めましょう。

よくある質問と失敗パターン(FAQ)

❓ トークンが改ざんされたらどうなるの?

署名(Signature)が一致しないため、サーバー側で verify() に失敗します。エラーを返して処理を中断できるので、改ざんは原理的に検知可能です。

❓ JWT にパスワードを含めてもいい?

**絶対に NG です。**JWT は暗号化されておらず、ブラウザやネットワーク上に露出する可能性があります。認証後の識別情報(ID やロールなど)に限定しましょう。

❓ トークンが期限切れになった場合はどうなる?

サーバー側で verify() 時に TokenExpiredError などが発生します。その場合、401 Unauthorized を返して、再ログインやリフレッシュ処理を促すのが一般的です。

❓ Cookie で保存すれば安全?

JavaScript からアクセスできない HttpOnly にしてあれば、XSS(クロスサイトスクリプティング)への耐性はあります。ただし、CSRF(クロスサイトリクエストフォージェリ)対策として SameSite=Strictトークン検証ロジック を併用しましょう。

❓ トークンの無効化はできないの?

JWT はステートレスなので、トークンの失効は基本的に「時間が経つのを待つ」しかありません。対策として:
  • リフレッシュトークン方式の導入
  • ブラックリスト DB で強制失効を管理(ただしステートフルになる) が必要です。

❓ dev 環境で Secure Cookie が送られないのはなぜ?

secure: true の Cookie は、HTTPS 通信でのみ送信される仕様です。ローカル開発時に HTTP で確認したい場合は、開発用に secure: false を設定して切り替えましょう(本番では必ず true に)。

❓ サーバーレス(Vercel や Cloudflare Workers)でも使える?

JWT はサーバーレス環境と相性が良く、ステートレスで軽量なため非常に人気です。ただし、署名用の秘密鍵を安全に管理できる環境が前提となります(環境変数や Secrets)。

まとめ:JWT は Next.js 時代の「認証の定番」になり得る

本記事では、Next.js でログイン機能を構築する際に活用できる認証手法「JWT」について、次のような観点から詳しく解説しました:
  • 認証方式としての JWT の立ち位置と他方式との比較(NextAuth.js/セッション方式)
  • JWT の基本構造と動作原理(Header/Payload/Signature)
  • セキュリティ設計上の注意点と Cookie 保存戦略
  • 実装例:ログイン API・保護ルート・RBAC・UI 連携
  • 初心者がつまずきやすい質問や失敗パターン
JWT は、サーバーに状態を持たず、トークンそのものに認証情報を詰め込む「ステートレス認証」の代表格です。API ベース、マイクロサービス構成、サーバーレス時代の認証方式として、ますます活用が広がっています。
ただし、その利便性の裏には「セキュリティの設計責任」が開発者側に大きく委ねられていることも忘れてはいけません。
次回の記事では:
  • JWT のトークンをPrismaと連携させてユーザーデータと結びつける方法
  • Next.js のApp Router における保護ルートの作成方法
  • ログインフォームとログイン状態保持のフロント実装
など、実践的な実装ステップに進んでいきます。
ぜひ次回もご覧ください!

参考文献・公式リソース

この記事の執筆にあたり、以下の公式資料や信頼性のある情報源を参考にしています:
読者の方がさらに深く理解を進める際の足がかりになれば幸いです。
JWT はサーバーレス環境と相性が良く、ステートレスで軽量なため非常に人気です。ただし、署名用の秘密鍵を安全に管理できる環境が前提となります(環境変数や Secrets)。
この記事の執筆・編集担当
DE

松本 孝太郎

DELOGs編集部/中年新米プログラマー

ここ数年はReact&MUIのフロントエンドエンジニアって感じでしたが、Next.jsを学んで少しずつできることが広がりつつあります。その実践記録をできるだけ共有していければと思っています。