JWT vs Session?もう迷わない!完全比較と意思決定ガイド

午前3時。私はパソコンの画面を前に、session: { strategy: "jwt" } と session: { strategy: "database" } の間でカーソルを行ったり来たりさせていました。プロジェクトのリリースは目前、それなのに私はまだ JWT にすべきか Session にすべきかで悩んでいたのです。ドキュメントを一通り見ても、「それぞれにメリット・デメリットがある」「シナリオに応じて選択せよ」といった、正しいけれど役には立たないアドバイスばかり。
あなたも似たような経験があるかもしれません。技術選定の際、どちらの案も良さそうに見えて、自分のプロジェクトにはどっちが合っているのか分からない。正直なところ、私が初めてこの問題に直面した時も混乱しましたし、いくつもの落とし穴にはまってようやく理解しました。今日は、JWT と Session という2つのセッション戦略について、本音で語り合いたいと思います。これを読めば、もう迷うことはありません。
まずは基本を理解しよう
JWT と Session、定義を暗記している人は多いですが、分かりやすく説明するのは意外と難しいものです。私はよくこんな例え話をします。
Session はジムの会員カードのようなものです。入会手続きをすると、ジムのシステムにあなたの会員情報が記録されます。ジムに行くたびにカードを提示(Session ID を提示)すれば、受付スタッフはシステムからあなたが誰で、会員期限がいつまでで、あと何回レッスンが残っているかを確認できます。重要な情報はすべてジムのデータベースにあります。
JWT は身分証明書のようなものです。氏名、誕生日、住所などの全情報がカード自体に印字されています。身分を証明する際はカードを見せるだけで、相手はあなたが誰か分かります。わざわざどこかのシステムに問い合わせる必要はありません。
こう考えると、両者の仕組みがはっきりと見えてきます。
Session 方式:ユーザーがログインすると、サーバーは Session ID を生成し、ユーザー情報をデータベースに保存します。そして Session ID を Cookie 経由でブラウザに渡します。その後のリクエストでは、ブラウザがこの Session ID を送信し、サーバーはそれを使ってデータベースからユーザー情報を検索します。
JWT 方式:ユーザーがログインすると、サーバーはユーザー情報を暗号化されたトークン(JWT)にパッケージ化し、ブラウザに直接渡します。その後のリクエストでは、ブラウザがこの JWT を送信し、サーバーはこのトークンの署名を検証するだけで済み、データベースを検索する必要はありません。
JWT を徹底解剖
なぜ JWT はこれほど人気なのか?
正直なところ、開発者の間、特に Serverless やマイクロサービスを扱うチームでは JWT が大人気です。理由はシンプル、データベースの心配が要らないからです。
私の友人が Vercel で EC サイトを立ち上げた際も、選んだのは JWT でした。彼曰く、最大のメリットは「楽」なこと。DB 接続数を気にする必要も、Session テーブルのパフォーマンスを心配する必要もなく、エッジノードへのデプロイも問題ありません。ユーザーが一度ログインして JWT を取得すれば、サーバーは署名を検証するだけで済むのです。
JWT は特に以下のシナリオに適しています:
1. Serverless / エッジコンピューティング
Vercel や Cloudflare Workers などのプラットフォームにデプロイする場合、JWT はほぼ標準です。これらのプラットフォームの関数はステートレスで、リクエストごとに異なるサーバーで処理される可能性があります。Session を使うなら Redis などでデータを共有する必要がありますが、JWT ならトークン自体に情報が含まれているので、どのサーバーで処理しても同じです。
2. 高並行処理シナリオ
秒読みセールなどを経験した人なら分かるでしょうが、データベースクエリはパフォーマンスのボトルネックになりがちです。Session を使うとリクエストごとに DB を引くことになりますが、JWT ならログイン時に一度引くだけ。その後の検証はローカルで完結するので非常に高速です。
3. マイクロサービスアーキテクチャ
複数のサービス(ユーザーサービス、注文サービス、決済サービスなど)がある場合、JWT を使えばサービス間で認証情報を共有できます。各サービスがユーザーセッションを個別に検索したり、統一された Session ストアを用意したりする必要はありません。
JWT の落とし穴
良いことづくめに聞こえますが、JWT にも問題はあり、その落とし穴は意外と深いです。
最大の落とし穴:即時撤回ができない
ある時、ユーザーアカウントが乗っ取られていることに気づき、すぐにそのユーザーをログアウトさせたいと思いました。Session なら DB のレコードを削除すれば終わりです。しかし JWT では……一度発行された JWT は、有効期限が切れるまで有効であり、取り消すことができません。
「ブラックリストを作れば?」と思うかもしれません。可能ですが、それでは JWT の「ステートレス」という利点が失われてしまいます。リクエストごとにブラックリストをチェックするなら、Session テーブルを引くのと何が違うのでしょうか?
ですから JWT を使う場合は、有効期限を短く(例えば15分)設定し、refresh token と組み合わせて使うのが一般的です。そうすれば、トークンが盗まれても被害は最大15分で済みます。
Cookie サイズの制限
JWT はユーザー情報をトークンにエンコードします。もし JWT にあまりに多くの情報(ユーザーロール、権限、設定など)を詰め込むと、トークンが肥大化します。ブラウザの Cookie には 4KB の制限があり、それを超えると保存できません。
教訓:何でもかんでも JWT に詰め込まないこと。ユーザーIDと有効期限など、最小限の基本情報だけにして、他の情報は必要な時に検索するようにしましょう。
NextAuth.js JWT 設定の実戦
Next.js を使っているなら、NextAuth.js(現在は Auth.js)が最も一般的な認証ソリューションです。JWT の設定はとても簡単です:
// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30日
},
callbacks: {
async jwt({ token, user }) {
// 初回ログイン時、user 情報を token に追加
if (user) {
token.id = user.id
token.role = user.role
}
return token
},
async session({ session, token }) {
// token 内の情報をクライアントに公開
if (token) {
session.user.id = token.id
session.user.role = token.role
}
return session
},
},
})いくつか注意点があります:
NEXTAUTH_SECRETは必ず設定すること(Auth.js v5 ではAUTH_SECRETになりました)。npx auth secretで安全なキーを生成できます。これは JWT の署名と暗号化に使われるので、絶対に漏洩させてはいけません。ローリングセッション:ユーザーがアクティブな間に自動更新したい場合は、
updateAgeを設定します:
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60,
updateAge: 24 * 60 * 60, // 24時間ごとに更新
}これで、ユーザーが24時間以内にアクティブであれば Session が自動更新され、突然ログアウトされることはありません。
- 2025年の新変更:Edge Runtime(middleware など)にデプロイする場合、設定を分割する必要があります:
// auth.config.ts - Edge で実行可能
export default {
providers: [GitHub],
session: { strategy: "jwt" },
}
// auth.ts - DB 操作を含み、Node.js でのみ実行
import { PrismaAdapter } from "@auth/prisma-adapter"
import authConfig from "./auth.config"
export const { handlers, auth } = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
})これは Edge Runtime が一部の Node.js API をサポートしていないためです。分割することで、middleware は auth.config.ts を使い、サーバーサイドロジックは完全な auth.ts を使うことができます。
Database Session を徹底解剖
いつ Session を使うべきか?
JWT は便利ですが、古き良き Session を使うべきシーンもあります。
以前、金融機関向けの管理システムを作ったことがあります。彼らの要件は明確でした。「不正ログイン検知時には、即座にユーザーをキックアウトできること」。この場合、JWT は不向きです。
Database Session は以下のシーンに適しています:
1. 即時制御が必要なアプリ
複数端末ログイン制限(1つのデバイスのみ許可)、強制ログアウト(管理者によるキック)、パスワード変更後の全ログイン無効化など。これらは Session なら簡単に実現できますが、JWT では追加のロジックが必要です。
2. セキュリティ要件が高いシーン
銀行、医療、政府機関などでは、リクエストごとに最新の権限状態を確認する必要があります。Session ならリクエストのたびに DB をチェックするので、権限変更が即座に反映されます。
3. 従来のモノリシックアプリ
サーバーとデータベースが一緒にある従来のモノリシックな構成なら、Session の負担は大きくありません。DB クエリの遅延は低く、セッション制御もしやすいです。
Session のパフォーマンス問題
最大の問題は、リクエストごとにデータベースクエリが発生することです。トラフィックが多いアプリでは、これがボトルネックになり得ます。
最適化の方法はいくつかあります:
Redis に Session を保存:従来の DB より遥かに高速で、自動期限切れもサポートしています。
Session 情報を最小化:ユーザーIDだけを保存し、他の情報は必要な時だけ取得する。
有効期限の適切な設定:期限切れの Session は定期的に削除しないと、テーブルが肥大化します。
Next.js Session 設定の実戦
NextAuth.js はデフォルトで JWT を使用しますが、Database Session を使いたい場合は adapter の設定が必要です:
// auth.ts
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import GitHub from "next-auth/providers/github"
const prisma = new PrismaClient()
export const { handlers, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [GitHub],
session: {
strategy: "database",
maxAge: 30 * 24 * 60 * 60, // 30日
updateAge: 24 * 60 * 60, // 毎日更新
},
})Prisma を使う場合、Session テーブルを作成する必要があります。npx prisma db push で自動作成されます:
// schema.prisma
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
email String @unique
sessions Session[]
}パフォーマンス最適化のコツ:
sessionTokenとexpiresフィールドにインデックスを貼ることで、検索が高速化します。期限切れ Session の定期削除タスクを作成する:
// cleanup-sessions.ts
async function cleanupExpiredSessions() {
await prisma.session.deleteMany({
where: {
expires: {
lt: new Date(),
},
},
})
}
// 毎日午前3時に実行実戦的意思決定フレームワーク
理屈は分かりましたが、結局どう選べばいいのでしょうか? 私がまとめたフレームワークを使って、あなたのプロジェクトをチェックしてみてください:
プロジェクトタイプで選ぶ
| プロジェクトタイプ | 推奨案 | 理由 |
|---|---|---|
| Serverless アプリ | JWT | ステートレス、共有 Session ストレージ不要 |
| 従来型モノリシック | Session | DB が近くにあり、クエリコストが低い |
| マイクロサービス | JWT / ハイブリッド | サービス間の認証共有が容易 |
| コンテンツ/ブログ | JWT | 認証要件がシンプルで JWT で十分 |
| SaaS 管理画面 | Session | きめ細かい権限管理が必要 |
セキュリティ要件で選ぶ
- 低敏感(ブログ、フォーラム、個人サイト):JWT、有効期限30日
- 中敏感(EC、SNS):JWT + 短い有効期限 + refresh token
- 高敏感(金融、医療、管理画面):Session、短い有効期限 + ローリングセッション
ケーススタディ
ケース1:EC サイト - JWT を選択
友人の EC サイトは最終的に JWT を選びました。理由は:
- Vercel の Serverless アーキテクチャにデプロイするため
- ユーザー数が多く、DB クエリ削減でコストを下げたい
- セッション制御の要件が高くなく、ログアウト後のトークン失効に多少のラグがあっても許容できる
唯一の妥協点は、JWT の有効期限を通常の30日ではなく7日にしたことです。アカウントが乗っ取られても、7日で自動的に失効するようにしました。
ケース2:社内 OA システム - Session を選択
別の社内 OA システムでは Session を選びました:
- 厳格な権限管理が必要で、役割変更は即時反映したい
- 同時ログイン端末数を制限したい
- イントラネット配置で、DB 遅延は無視できる
このシナリオでは Session が明らかに最適です。Redis を採用し、レスポンスは 50ms 以内で全く問題ありません。
ケース3:ハイブリッド - Clerk の手法
今時の認証プラットフォーム(Clerk など)はハイブリッド方式を採用しています:
- 短期トークン (JWT):有効期限15分、API 呼び出し用
- 長期トークン (Refresh Token):DB に保存、短期トークンの更新用
- Session 記録:DB にアクティブな Session を記録し、リモートで無効化可能
JWT の性能と Session の制御力を両立しています。実装は複雑ですが、セキュリティと UX 両方が求められる場合に適しています。
私のアドバイス
まだ迷っているなら、以下をお勧めします:
基本は JWT を使う:ほとんどのシーンで十分ですし、設定も簡単で高速です。
以下の場合は Session に切り替える:
- 複数端末ログイン制限が必要
- 即時キックアウト機能が必要
- 金融/医療などの高セキュリティ
- パスワード変更後の全ログイン即時無効化が必要
過剰設計しない:小規模プロジェクトなら簡単な方を選び、ボトルネックが来てから最適化しましょう。最初からハイブリッド構成にして複雑になりすぎ、実際には使わない機能ばかり……という例を山ほど見てきました。
セッション期限切れのベストプラクティス
採用案が決まったら、期限切れの処理を考えましょう。ここを間違えると UX が最悪になります。
JWT の期限切れ処理
JWT で一番困るのは、ユーザーがフォーム入力中に「ログインが切れました」と言われて内容が全部消えることです。
解決策:Refresh Token + サイレント更新
考え方はこうです:
ユーザーに2つのトークンを発行:
- Access Token:有効期限15分、API 用
- Refresh Token:有効期限30日、Access Token 更新用
フロントエンドは Access Token が切れそうになったら(残り2分など)、Refresh Token で新しい Access Token を取得する
ユーザーは気づかず、ログアウトされることもない
NextAuth.js には内蔵されています。設定するだけです:
callbacks: {
async jwt({ token, user, account }) {
if (account && user) {
// 初回ログイン
return {
...token,
accessToken: account.access_token,
accessTokenExpires: Date.now() + account.expires_in * 1000,
refreshToken: account.refresh_token,
}
}
// Access Token はまだ有効
if (Date.now() < token.accessTokenExpires) {
return token
}
// 期限切れ、Refresh Token で更新
return refreshAccessToken(token)
},
}Session の期限切れ処理
Session は比較的シンプルですが、こちらもコツがあります。
ローリングセッション vs 絶対タイムアウト
- ローリングセッション:ユーザーがアクティブなら延長。一般的。
- 絶対タイムアウト:アクティブでも時間になれば強制ログアウト。銀行などに適する。
NextAuth.js のローリングセッション設定:
session: {
strategy: "database",
maxAge: 2 * 60 * 60, // 2時間で絶対タイムアウト
updateAge: 30 * 60, // 30分以内に活動があれば更新
}アイドルタイムアウト
「15分操作がなければログアウト」を実現したい場合、フロントエンドにタイマーを仕込みます。
let idleTimer
function resetIdleTimer() {
clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
// 15分操作なしでログアウト
signOut()
}, 15 * 60 * 1000)
}
// ユーザーのアクションを監視
document.addEventListener("mousemove", resetIdleTimer)
document.addEventListener("keypress", resetIdleTimer)2025年の新トレンド
最後に、今年のトレンドについて少し触れておきましょう。
ハイブリッドが主流に
JWT と Session は排他的なものではないという認識が広まっています。
- API 認証は JWT(速い)
- アクティブ Session 管理は DB(制御可)
- 両者のいいとこ取り
Clerk や Auth0 はこのアプローチです。自分で実装するのが面倒なら、これらのサービスを使うのも手です。
Edge Runtime の影響
Next.js の middleware は Edge Runtime で動くため、JWT と相性が良いです。認証ロジックを middleware(/dashboard 全体を保護するなど)に置くなら、JWT の方が便利です。
Passkey と WebAuthn
今回の主題ではありませんが、Passkey(WebAuthn ベース)が普及し、指紋や Face ID でのログインが増えています。認証フローは複雑になりますが、UX は向上します。NextAuth.js v5 もサポートしています。
最後に
JWT か Session か、明確な答えはありません。重要なのはそれぞれの特性を理解し、自分のプロジェクトに合わせて選ぶことです。
私の経験では、ほとんどのプロジェクトは JWT で十分。即時制御が必要な場合だけ Session。最初から複雑にせず、簡単な方から始めましょう。
迷っているなら、とりあえず NextAuth.js のデフォルト(JWT)で始めてみてはどうでしょう? 問題が出たら Session に切り替えるのも、設定を数行変えるだけです。
もし今も深夜3時で悩んでいるなら、とりあえず寝ましょう。一晩寝れば、案外すんなり決まるものです(笑)。
FAQ
JWT と Session の決定的な違いは何ですか?
いつ JWT を使い、いつ Session を使うべきですか?
JWT は即時無効化できませんが、どうすればいいですか?
Session のパフォーマンスはどう改善できますか?
NextAuth.js での設定方法は?
セッション切れの UX 対策は?
2025年のトレンドは?
8 min read · 公開日: 2025年12月19日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践

Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド

Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド


コメント
GitHubアカウントでログインしてコメントできます