JWT か Session?もう迷わない、読めば要点が掴める
session: { strategy: "jwt" } と session: { strategy: "database" } の間で、カーソルが点滅している——リリース直前なのに、JWT と Session のどちらにするか決めきれない、そんな夜を過ごしたことはありませんか。
技術選定では、どちらも一見合理的に見えます。JWT はステートレスで DB を引かない一方、即時失効はできません。Session はステートフルで即時制御できるが、リクエストごとに DB を参照します。本記事では、この 2 つのセッション戦略の違いと選び方を整理します。
まずは基本を押さえる
JWT と Session、定義は言える人が多いのに、説明すると意外と難しい。私はこんな比喩が好きです。
Session はジムの会員カードです。入会時にジムのシステムへ会員情報が登録されます。来店のたびにカード(Session ID)を提示すれば、受付はシステムから氏名・期限・残回数を確認できます。重要な情報はすべてジム側の DB にあります。
JWT は身分証明書です。氏名・生年月日・住所などがカード自体に印字されています。提示するだけで相手はあなたを特定でき、別システムへ問い合わせる必要はありません。
仕組みはこう整理できます。
-
Session 方式:ログイン後、サーバーが Session ID を生成し、ユーザー情報を DB に保存。Session ID を Cookie でブラウザへ返し、以降のリクエストで ID を受け取って DB からユーザー情報を取得する。
-
JWT 方式:ログイン後、サーバーがユーザー情報を暗号化トークン(JWT)にまとめてブラウザへ返す。以降は JWT を送ってもらい、サーバーは署名検証だけでよく、DB 参照は不要。
JWT を深掘りする
なぜ JWT が人気なのか
JWT は、とくに Serverless やマイクロサービスを扱うチームで支持されています。理由はシンプル。DB を気にしなくていいからです。
友人が Vercel 上の EC サイトで JWT を選んだとき、「楽」が最大の理由でした。接続数や Session テーブルの性能を心配せず、エッジへそのまま載せられる。一度ログインして JWT を受け取れば、以降は署名検証だけで済みます。
JWT が向くシーンは次のとおりです。
1. Serverless / エッジコンピューティング
Vercel や Cloudflare Workers では JWT が定番です。関数はステートレスで、リクエストごとに別サーバーで処理されることもあります。Session なら Redis などで共有ストアが要りますが、JWT ならトークンに情報が載っているので、どのサーバーでも同じです。
2. 高並行シナリオ
秒殺セールを経験した人なら分かるでしょう。DB クエリはボトルネックになりがちです。Session だとリクエストごとに DB、JWT ならログイン時だけ DB を引き、以降はローカル検証で速い。
3. マイクロサービス
ユーザー・注文・決済など複数サービスがある場合、JWT で認証情報を共有できます。各サービスが Session を個別に引いたり、共通 Session ストアを用意したりする必要がありません。
JWT の落とし穴
良さばかりではありません。特に深いのが次の点です。
最大の落とし穴:即時失効できない
あるときアカウント乗っ取りが判明し、即座にログインを切りたい場面がありました。Session なら DB レコードを消すだけ。JWT だと、発行後は期限まで有効で、取り消せません。
「ブラックリストを?」——可能ですが、リクエストごとに blacklist を引くなら Session と大差ありません。ステートレスの利点が薄れます。
そのため JWT では期限を短く(例:15 分)し、refresh token と組み合わせるのが一般的です。盗まれても被害は最大 15 分で済みます。
Cookie サイズ制限
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. 従来型モノリス
サーバーと DB が同居する構成なら、Session の DB コストは小さく、制御もしやすい。
Session の性能問題
最大の課題はリクエストごとの DB 参照です。トラフィックが大きいとボトルネックになります。
最適化の例:
- Redis に Session を保存——従来 DB より速く、自動失効も可能。
- 情報を最小化——ユーザー 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 | 認証要件がシンプル |
| SaaS 管理画面 | Session | きめ細かい権限制御 |
セキュリティ要件で選ぶ
- 低感度(ブログ、フォーラム、コンテンツ):JWT、30 日期限
- 中感度(EC、SNS):JWT + 短期限 + refresh token
- 高感度(金融、医療、管理画面):Session + 短期限 + ローリングセッション
ケーススタディ
ケース 1:EC サイト — JWT
友人の EC サイトは JWT を選択。理由は次のとおり。
- Vercel の Serverless 構成
- ユーザー数が多く、DB クエリ削減でコスト低減
- セッション制御要件が緩く、ログアウト後の失効ラグは許容
妥協点は期限を 30 日ではなく 7 日にしたこと。乗っ取られても 7 日で自動失効します。
ケース 2:社内 OA — Session
社内 OA は Session を選択。
- 厳格な権限管理、ロール変更の即時反映
- 同時ログイン端末数の制限
- イントラネット配置で DB 遅延は無視できる
Redis で Session を保存し、レスポンス 50ms 以内で十分でした。
ケース 3:ハイブリッド — Clerk のやり方
Clerk などの認証 SaaS はハイブリッドです。
- 短期トークン(JWT):15 分、API 用
- 長期トークン(Refresh Token):DB 保存、短期トークン更新用
- Session 記録:DB にアクティブ Session を残し、リモート失効可能
JWT の速度と Session の制御を両立。実装は複雑ですが、セキュリティと UX 双方が厳しい場面向き。
私の提案
迷ったら次を基準に。
- 基本は JWT——多くのプロジェクトで十分。設定もシンプルで速い。
- 次の場合は Session——端末数制限、即時キック、金融/医療、パスワード変更後の全セッション失効。
- 過剰設計しない——小規模ならまず単純な案。ボトルネックが来てから最適化。最初からハイブリッドで複雑化し、使わない機能だけ増える例をよく見ます。
セッション期限切れのベストプラクティス
方式を決めたら、期限切れ処理も設計しましょう。ここが雑だと UX が一気に悪化します。
JWT の期限切れ
いちばん困るのは、フォーム入力中に「ログイン期限切れ」で内容が消えること。
解:Refresh Token + 無感リフレッシュ
流れ:
- 2 種類のトークンを発行
- Access Token:15 分、API 用
- Refresh Token:30 日、Access Token 更新用
- Access Token が切れそう(残り 2 分など)になったら、フロントが Refresh Token で新しい Access Token を取得
- ユーザーは気づかず、突然ログアウトされない
NextAuth.js でも callbacks で実装できます。
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
}
// Access Token 失効、Refresh Token で更新
return refreshAccessToken(token)
},
}
async function refreshAccessToken(token) {
try {
const response = await fetch("https://api.example.com/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.OAUTH_CLIENT_ID,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
}),
})
const refreshedTokens = await response.json()
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
}
} catch (error) {
return {
...token,
error: "RefreshAccessTokenError",
}
}
}
Session の期限切れ
Session は比較的シンプルですが、設計の選択肢があります。
ローリングセッション vs 絶対タイムアウト
- ローリング:操作があれば延長。一般的。
- 絶対:操作があっても時間で強制ログアウト。銀行など高セキュリティ向き。
NextAuth.js の例:
session: {
strategy: "database",
maxAge: 2 * 60 * 60, // 2時間で絶対タイムアウト
updateAge: 30 * 60, // 30分以内の操作で更新
}
30 分以内に操作があれば延長、ただし 2 時間で必ずログアウト。
アイドルタイムアウト
「15 分操作なしでログアウト」はフロントにタイマーを置きます。
let idleTimer
function resetIdleTimer() {
clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
// 15分操作なしでログアウト
signOut()
}, 15 * 60 * 1000)
}
// ユーザー操作を監視
document.addEventListener("mousemove", resetIdleTimer)
document.addEventListener("keypress", resetIdleTimer)
2025 年のトレンド
今年(2025)の流れも、選択に影響します。
ハイブリッドが主流
JWT と Session は排他ではありません。
- API 認証は JWT(速い)
- アクティブ Session は DB に記録(制御可)
- 両方の良いとこ取り
Clerk や Auth0 がこの路線。自前実装が面倒なら SaaS も手です。
Edge Runtime の影響
Next.js の middleware は Edge Runtime 上で動き、JWT と相性が良いです。/dashboard 全体を middleware で保護するなら 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 で JWT と Session をどう設定する?
セッション期限切れはどう扱い、UX をどう改善する?
2025 年の JWT/Session の新トレンドは?
5分で読めます · 公開日: 2025年12月19日 · 更新日: 2026年6月8日
関連記事
セルフホスト Dev Sandbox:Docker と Go でプレビュー環境を作る
セルフホスト Dev Sandbox:Docker と Go でプレビュー環境を作る
Cloudflare Pro か Business か?3 つの軸で判断するアップグレード判断ツリー
Cloudflare Pro か Business か?3 つの軸で判断するアップグレード判断ツリー
社内ネットワーク Docker pull タイムアウトのトラブルシューティング:DNS・プロキシ・ミラー加速の完全ガイド
コメント
GitHubアカウントでログインしてコメントできます