Next.js認証の深堀り:JWTという『透明な証明書』をどう扱うか?
Next.js(App Router)での認証実装。ライブラリが優秀すぎて「なぜか動く」状態になりがちですが、一歩踏み込んで JWT(JSON Web Token ジョット) の仕組みを理解すると、デバッグの質が劇的に変わります。
今回は、Auth.js(NextAuth)を例に、JWTの正体と現場で使えるTipsをまとめました。
認証は「パスポート」と「搭乗券」のリレー
Section titled “認証は「パスポート」と「搭乗券」のリレー”Webサイトの認証フローは、空港のプロセスに例えると非常にクリアになります。
- ログイン(認証): ID/PWを提示して本人確認をする(パスポート提示)。
- トークン発行: サーバーが「この人は本人です」という証明書を発行する(搭乗券の発行)。
- アクセス(認可): 以降、ブラウザがその証明書を自動で提示する。
この「搭乗券」の役割を果たすのが JWT です。
JWT(JSON Web Token)とは?
Section titled “JWT(JSON Web Token)とは?”現在、Next.jsなどのモダンな開発で最も使われているトークンの形式が JWT です。
最大の特徴は、 「サーバーがいちいちデータベースを見に行かなくても、その中身を見ただけで正当性がわかる」 という点にあります。
JWTを分解すると、ドット(.)で区切られた3つのパーツでできています。
| パーツ名 | 例え | 内容 |
|---|---|---|
| ヘッダー | 封筒の種類 | 「これはJWTです」「暗号化の種類はこれです」という情報。 |
| ペイロード | 証明書の本文 | 「ユーザーID:123」「有効期限:2026/02/07」などの実データ。 |
| 署名 (Signature) | 偽造防止のハンコ | サーバーだけが知る「秘密の鍵」で押された電子印鑑。 |
追記: JSON Web Token(JWT)デバッガー で実際のJWTを貼り付けると、各パーツの中身を簡単に確認できます。
JWTは「透明なガラスケース」
Section titled “JWTは「透明なガラスケース」”JWTについて最も誤解されやすいのは、 「中身は暗号化されていない」 という点です。
JWTはドット(.)で区切られた3つのパートで構成されていますが、本文(ペイロード)は単にBase64で変換されているだけ。誰でも中身を読めてしまいます。
❌ NG例:機密情報を入れてしまう
Section titled “❌ NG例:機密情報を入れてしまう”{ "userId": "123", "password": "hashed_password_abc", // 絶対にダメ! "address": "東京都..." // 個人情報も避けるべき}✅ OK例:識別子と期限に留める
Section titled “✅ OK例:識別子と期限に留める”{ "sub": "user_8k92jL0s", // ユーザーID "role": "ADMIN", // 権限 "exp": 1709859015 // 有効期限}「中身は見えてもいいが、署名(ハンコ)があるから書き換えはできない」。これがJWTの鉄則です。
現場で使える「デバッグとテスト」の実践
Section titled “現場で使える「デバッグとテスト」の実践”1. ターミナルでJWTを覗き見る
Section titled “1. ターミナルでJWTを覗き見る”Auth.jsのデフォルトではCookieが暗号化されているため、ブラウザのデベロッパーツールから直接中身を読むのは困難です。
そんな時は auth.ts の jwt コールバックにログを仕込むのが最速です。
callbacks: { async jwt({ token, user }) { if (user) { token.role = user.role; } console.log("DEBUG [JWT]:", token); // サーバー側のログで規約通りの sub や exp を確認 return token; },}2. 安全なテストログインの実装
Section titled “2. 安全なテストログインの実装”開発中、いちいちGoogleログインをするのは手間ですよね。かといってコードにパスワードを直書きするのは論外です。
以下のように環境変数と NODE_ENV を組み合わせるのが「エンジニアの作法」です。
Credentials({ authorize: async (credentials) => { if (process.env.NODE_ENV === "production") return null; // 本番では絶対動作させない
if ( credentials.email === process.env.TEST_ADMIN_EMAIL && credentials.password === process.env.TEST_ADMIN_PASSWORD ) { return { id: "test-id", role: "ADMIN" }; } return null; },})補足:規約を知る
Section titled “補足:規約を知る”JWTには RFC 7519 という国際規格があります。
特に、JWTの中身(ペイロード)に入れる項目には、規約で決められた 「予約済みクレーム(Reserved Claims)」 という特別な名前があります。
規約では、項目の名前を3文字に省略することが推奨されています。これは、データ量を少しでも小さくして通信速度を上げるためです。
よく使われる予約済みクレーム
| 項目名 | 正称 | 意味 |
|---|---|---|
iss | Issuer | 発行者(誰がこのトークンを作ったか。例:Googleなど) |
sub | Subject | 一意識別子(誰のためのトークンか。通常はユーザーID) |
aud | Audience | 対象者(誰がこのトークンを使う予定か。例:自分のアプリのURL) |
exp | Expiration Time | 有効期限(これ以降は無効。UNIXタイムスタンプで記述) |
iat | Issued At | 発行時刻(いつ作られたか) |
jti | JWT ID | トークン独自のID(再利用攻撃を防ぐためのシリアル番号) |
なぜユーザーIDが id ではなく sub なのか、なぜ期限が exp なのか。これらの「3文字の規約」を知っておくと、Auth.jsから別のライブラリ(Better Authなど)へ移行した際や、Supabase等のBaaSを使う際も、共通の知識として活用できます。
「なんとなく動く認証」から「仕組みを理解した堅牢な認証」へ。 皆さんのNext.js開発の一助になれば幸いです。