言語を切り替える
テーマを切り替える

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 を送信し、サーバーはこのトークンの署名を検証するだけで済み、データベースを検索する必要はありません。

0回
JWT DB クエリ
後続のリクエストで DB 検索不要
毎回
Session DB クエリ
リクエストごとに DB 検索が必要
15分
JWT 推奨有効期限
refresh token と組み合わせる
4KB
Cookie サイズ制限
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
    },
  },
})

いくつか注意点があります:

  1. NEXTAUTH_SECRET は必ず設定すること(Auth.js v5 では AUTH_SECRET になりました)。npx auth secret で安全なキーを生成できます。これは JWT の署名と暗号化に使われるので、絶対に漏洩させてはいけません。

  2. ローリングセッション:ユーザーがアクティブな間に自動更新したい場合は、updateAge を設定します:

session: {
  strategy: "jwt",
  maxAge: 30 * 24 * 60 * 60,
  updateAge: 24 * 60 * 60, // 24時間ごとに更新
}

これで、ユーザーが24時間以内にアクティブであれば Session が自動更新され、突然ログアウトされることはありません。

  1. 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 のパフォーマンス問題

最大の問題は、リクエストごとにデータベースクエリが発生することです。トラフィックが多いアプリでは、これがボトルネックになり得ます。

最適化の方法はいくつかあります:

  1. Redis に Session を保存:従来の DB より遥かに高速で、自動期限切れもサポートしています。

  2. Session 情報を最小化:ユーザーIDだけを保存し、他の情報は必要な時だけ取得する。

  3. 有効期限の適切な設定:期限切れの 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[]
}

パフォーマンス最適化のコツ

  1. sessionTokenexpires フィールドにインデックスを貼ることで、検索が高速化します。

  2. 期限切れ Session の定期削除タスクを作成する:

// cleanup-sessions.ts
async function cleanupExpiredSessions() {
  await prisma.session.deleteMany({
    where: {
      expires: {
        lt: new Date(),
      },
    },
  })
}

// 毎日午前3時に実行

実戦的意思決定フレームワーク

理屈は分かりましたが、結局どう選べばいいのでしょうか? 私がまとめたフレームワークを使って、あなたのプロジェクトをチェックしてみてください:

プロジェクトタイプで選ぶ

プロジェクトタイプ推奨案理由
Serverless アプリJWTステートレス、共有 Session ストレージ不要
従来型モノリシックSessionDB が近くにあり、クエリコストが低い
マイクロサービス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 両方が求められる場合に適しています。

私のアドバイス

まだ迷っているなら、以下をお勧めします:

  1. 基本は JWT を使う:ほとんどのシーンで十分ですし、設定も簡単で高速です。

  2. 以下の場合は Session に切り替える

    • 複数端末ログイン制限が必要
    • 即時キックアウト機能が必要
    • 金融/医療などの高セキュリティ
    • パスワード変更後の全ログイン即時無効化が必要
  3. 過剰設計しない:小規模プロジェクトなら簡単な方を選び、ボトルネックが来てから最適化しましょう。最初からハイブリッド構成にして複雑になりすぎ、実際には使わない機能ばかり……という例を山ほど見てきました。

セッション期限切れのベストプラクティス

採用案が決まったら、期限切れの処理を考えましょう。ここを間違えると UX が最悪になります。

JWT の期限切れ処理

JWT で一番困るのは、ユーザーがフォーム入力中に「ログインが切れました」と言われて内容が全部消えることです。

解決策:Refresh Token + サイレント更新

考え方はこうです:

  1. ユーザーに2つのトークンを発行:

    • Access Token:有効期限15分、API 用
    • Refresh Token:有効期限30日、Access Token 更新用
  2. フロントエンドは Access Token が切れそうになったら(残り2分など)、Refresh Token で新しい Access Token を取得する

  3. ユーザーは気づかず、ログアウトされることもない

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 はステートレスで、ユーザー情報がトークンに含まれており、サーバーは署名検証のみで DB 参照が不要です。Session はステートフルで、情報は DB にあり、リクエストごとの DB 参照が必要です。JWT は Serverless/高並行向け、Session は即時制御が必要なシーン向けです。
いつ JWT を使い、いつ Session を使うべきですか?
JWT:Serverless/エッジ、高並行、マイクロサービス、コンテンツサイト。Session:即時制御(端末制限、強制ログアウト)、高セキュリティ(金融/医療)、従来型モノリシック。基本は JWT で、制御が必要な場合 Session に切り替えましょう。
JWT は即時無効化できませんが、どうすればいいですか?
有効期限を短く(15分)設定し Refresh Token を併用する、ブラックリストを使う(ステートレスの利点は消えますが)、あるいは Session/ハイブリッドに切り替える、などが対策です。多くの場合は短時間での有効期限切れで許容されます。
Session のパフォーマンスはどう改善できますか?
Redis を使用する、保存する情報を最小限(IDのみ)にする、インデックスを適切に貼る、期限切れ Session を定期的に削除する、といった方法があります。
NextAuth.js での設定方法は?
JWT:session: { strategy: 'jwt', maxAge: 30日 }。Session:PrismaAdapter 等を使用し session: { strategy: 'database' }(Session テーブル作成が必要)。Edge Runtime の場合は設定ファイルの分割が必要です。
セッション切れの UX 対策は?
JWT:Access Token(短)+ Refresh Token(長)でサイレント更新。Session:ローリングセッションでアクティブ時に延長。フォーム入力中のログアウトを防ぐ工夫が必要です。
2025年のトレンドは?
ハイブリッド(JWT で API 認証 + DB で Session 管理)が主流。Edge Runtime と相性の良い JWT が好まれる傾向。Passkey/WebAuthn の普及。

8 min read · 公開日: 2025年12月19日 · 更新日: 2026年1月22日

コメント

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

関連記事