TOOLS BOX/ガイド/JWT vs DB Session 比較
Concept

JWT vs DB Session 比較

Session ストレージ方式として JWT(署名済みトークン)と DB Session(サーバー側ストア)を比較するガイド。無効化のしやすさ・権限変更の追従・スケーラビリティの違いを整理し、どちらを選ぶかの設計判断材料を提供する。

jwtsessioncookieauthenticationstatelessstatefulnextjssecurity

どういう場面で使うか

  • ·認証設計で JWT と DB Session のどちらを使うか判断したいとき
  • ·強制ログアウト・権限変更の即時反映が要件にあるとき(DB Session 寄りの判断になる)
  • ·DB アクセスを最小化してスケールさせたいとき(JWT 寄りの判断になる)
  • ·session-management-pattern を実装する前に保存方式を決めたいとき

注意点 / Pitfalls

  • ·「JWT の方が常に軽い」は誤解。ペイロードが大きいと Cookie サイズが増え、毎リクエストの転送コストが上がる
  • ·「DB Session の方が常に安全」は誤解。Cookie セキュリティオプション(httpOnly / sameSite / secure)はどちらでも必要
  • ·JWT は期限内に強制無効化できない。ブラックリスト(DB や Redis)を導入した時点でステートレスの利点は薄れる
  • ·DB Session でも Redis キャッシュを使えばリクエストごとの DB 負荷を抑えられる。stateful = スケールしないは誤解
  • ·JWT の署名検証は CPU コストがある。秘密鍵の管理・ローテーションも運用コストになる

何と混同しやすいか

補足

どちらが優れているかではなく、要件に合うかで選ぶ。強制ログアウト・権限変更の即時反映が必要なら DB Session。DB クエリを増やさずにスケールしたい、かつ無効化要件が緩いなら JWT。Next.js の session-management-pattern は DB Session を前提にしているが、JWT に切り替えてもパターン(getSession / requireSession の分離)は同じ構造で使える。

2 方式の構造

JWT(JSON Web Token)

[ログイン成功]
  → サーバーがペイロード(userId, role, exp)を秘密鍵で署名
  → 署名済みトークンをクライアントの Cookie に保存

[その後のリクエスト]
  → Cookie から JWT を取り出す
  → サーバーが署名を検証(DB クエリなし)
  → ペイロードからユーザー情報を取得

JWT はそれ自体にユーザー情報が含まれます。サーバーは状態を持たず(stateless)、検証は署名チェックのみで完結します。

DB Session

[ログイン成功]
  → サーバーがランダムな Session ID を生成して DB に保存
  → Session ID を Cookie に保存

[その後のリクエスト]
  → Cookie から Session ID を取り出す
  → DB で Session ID を検索してユーザー情報を取得

DB Session はトークンの中にユーザー情報を持ちません。Cookie にあるのは DB を参照するための ID のみです。

設計判断軸の比較

判断軸JWTDB Session
保存場所ペイロードをクライアント(Cookie)に保存Session ID のみ Cookie、データは DB
検証コスト署名チェックのみ(DB クエリなし)DB クエリが必要
即時無効化難しい(期限切れまで有効)簡単(DB から削除するだけ)
権限変更の反映Token 期限が切れるまで遅延する次のリクエストから即時反映
スケールサーバー間でセッション共有不要DB / Redis の共有が必要
Cookie サイズペイロード分大きくなるSession ID のみ(小さい)
秘密鍵の管理必要(ローテーション運用も発生)不要

無効化と権限変更の扱い

JWT の最大の制約は即時無効化ができないことです。

JWT 期限: 24 時間

15:00 パスワード変更 → 旧 JWT は 翌15:00 まで有効
15:00 不審なアクセスを検知 → 即時ログアウトできない
15:00 admin ロールを剥奪 → 期限まで admin として動作する

対処として「JWT ブラックリスト(DB / Redis)」を導入できますが、その時点でサーバー側に状態が必要になり、stateless の利点は薄れます。

DB Session は DB レコードを削除・更新するだけで即時反映できます。

// 強制ログアウト(DB Session)
await db.session.delete({ where: { id: sessionId } }); // 次のリクエストから無効

// ロール変更(DB Session)
await db.session.update({
  where: { userId },
  data: { role: "user" }, // 次のリクエストから新ロールが適用される
});

Cookie と組み合わせるときの考え方

どちらの方式でも Cookie を使う場合、セキュリティオプションは同じ設定が必要です。

// JWT を Cookie に保存する場合
response.cookies.set("token", jwt, {
  httpOnly: true,   // JS から読めないようにする(XSS 対策)
  sameSite: "lax",  // CSRF 対策
  secure: true,     // HTTPS のみ
  maxAge: 60 * 60 * 24, // 24 時間
});

// DB Session の Session ID を Cookie に保存する場合
response.cookies.set("session_id", sessionId, {
  httpOnly: true,
  sameSite: "lax",
  secure: true,
  maxAge: 60 * 60 * 24 * 7, // 7 日間
});

違いはCookie に何を入れるかだけです。JWT はユーザー情報を含む署名済みトークン、DB Session はランダムな ID です。Cookie のセキュリティオプションはどちらも同じルールで設定します。

向いている場面

JWT が向く場面

  • 強制ログアウト・権限変更の即時反映が不要なシステム
  • 複数サービスにまたがる認証(マイクロサービス)でセッション共有を避けたい
  • DB クエリを認証のたびに発行したくない
  • 外部サービスとトークンを共有する設計(OAuth 連携など)

DB Session が向く場面

  • 強制ログアウトが必要(セキュリティ要件が高いシステム)
  • ロール変更・権限剥奪を即時反映したい(管理機能があるアプリ)
  • セッションデータが大きく Cookie に収めにくい
  • ユーザーのオンライン状態や最終アクセス時刻を管理したい

よくある誤解

「JWT の方が常に軽い」

JWT のペイロードが大きくなると Cookie サイズが増え、毎リクエストのヘッダー転送量が増えます。DB Session の Session ID(数十バイト)より JWT(数百バイト〜 KB 超)の方が大きいケースが多いです。

「DB Session の方が常に安全」

Cookie のセキュリティ(httpOnly / sameSite / secure)はどちらの方式でも同じく必要です。JWT か DB Session かよりも Cookie オプションの設定が正しいかどうかの方が実際のセキュリティに影響します。

「DB Session はスケールしない」

DB Session は Redis などのインメモリキャッシュと組み合わせることで、リクエストごとの DB 負荷を抑えられます。stateful = スケールしないという思い込みは誤りです。

「JWT にしておけばステートレスになる」

即時無効化のためにブラックリストを Redis で管理した時点で、サーバーは状態を持ちます。「ステートレスの維持」が実装上の優先事項かどうかを要件から確認してから選びましょう。

session-management-pattern との接続

session-management-pattern(getSession / requireSession の責務分離)はJWT でも DB Session でも同じ構造で使えます

// DB Session の場合(DB クエリで取得)
export const getSession = cache(async (): Promise<Session | null> => {
  const sessionId = cookieStore.get("session_id")?.value;
  if (!sessionId) return null;
  return db.session.findUnique({ where: { id: sessionId } });
});

// JWT の場合(署名検証で取得)
export const getSession = cache(async (): Promise<Session | null> => {
  const token = cookieStore.get("token")?.value;
  if (!token) return null;
  try {
    return verify(token, SECRET) as Session; // 署名検証
  } catch {
    return null;
  }
});

getSession() の内部実装が変わるだけで、呼び出し側(requireSession / requireRole / ページコンポーネント)は変わりません。


Session 取得 API の責務分離 → 型安全な Session 管理パターン

認証判定をどこに置くか → Next.js 認証方式比較

Cookie オプションの設定 → Cookie セキュリティオプション 3点セット

未認証・権限不足の失敗分岐 → 認証・権限チェックの失敗分岐

関連ドキュメント

関連サンプル

同じテーマや技術スタックを使った実装例