jsonwebtoken の使い方 — Node.jsでJWT認証を実装する
一言でいうと
jsonwebtoken は、Node.js で JSON Web Token(JWT)の生成・検証・デコードを行うための定番ライブラリです。HMAC(対称鍵)と RSA/ECDSA(非対称鍵)の両方に対応し、RFC 7519 に準拠した JWT を扱えます。
どんな時に使う?
- API認証: Express や Fastify などのバックエンドで、ステートレスなトークンベース認証を実装したい時
- マイクロサービス間通信: サービス間でユーザー情報や権限を安全に受け渡したい時
- パスワードリセットやメール認証: 有効期限付きの一時トークンを発行して、リンク経由で本人確認を行いたい時
インストール
# npm
npm install jsonwebtoken
# yarn
yarn add jsonwebtoken
# pnpm
pnpm add jsonwebtoken
TypeScript を使う場合は型定義も追加します。
npm install -D @types/jsonwebtoken
基本的な使い方
最も一般的なパターンは「トークンの発行 → トークンの検証」です。
import jwt from 'jsonwebtoken';
const SECRET = 'your-256-bit-secret';
// 1. トークンを発行する
const token = jwt.sign(
{ userId: 123, role: 'admin' },
SECRET,
{ expiresIn: '1h' }
);
console.log(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQ...
// 2. トークンを検証する
try {
const decoded = jwt.verify(token, SECRET);
console.log(decoded);
// { userId: 123, role: 'admin', iat: 1700000000, exp: 1700003600 }
} catch (err) {
console.error('トークンが無効です:', err);
}
よく使うAPI
1. jwt.sign() — トークンの生成
import jwt, { SignOptions } from 'jsonwebtoken';
const SECRET = 'my-secret-key';
// 同期的に生成(HMAC SHA256 がデフォルト)
const token = jwt.sign({ userId: 1 }, SECRET, {
expiresIn: '2h', // 有効期限: 2時間
issuer: 'my-app', // 発行者
audience: 'my-client', // 対象者
subject: 'auth', // 用途
} as SignOptions);
// 非同期で生成(コールバック版)
jwt.sign({ userId: 1 }, SECRET, { expiresIn: '2h' }, (err, token) => {
if (err) {
console.error('署名に失敗:', err);
return;
}
console.log('生成されたトークン:', token);
});
2. jwt.sign() with RSA — 非対称鍵での署名
import jwt from 'jsonwebtoken';
import fs from 'fs';
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');
// RSA SHA256 で署名
const token = jwt.sign(
{ userId: 42, permissions: ['read', 'write'] },
privateKey,
{ algorithm: 'RS256', expiresIn: '30m' }
);
// 公開鍵で検証
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
console.log(decoded);
3. jwt.verify() — トークンの検証
import jwt, { JwtPayload, VerifyOptions } from 'jsonwebtoken';
const SECRET = 'my-secret-key';
function verifyToken(token: string): JwtPayload {
const options: VerifyOptions = {
algorithms: ['HS256'], // 許可するアルゴリズムを明示
issuer: 'my-app', // 発行者の一致を検証
audience: 'my-client', // 対象者の一致を検証
clockTolerance: 5, // 時刻のずれを5秒まで許容
};
const decoded = jwt.verify(token, SECRET, options);
// string が返る場合もあるため型ガード
if (typeof decoded === 'string') {
throw new Error('Unexpected token format');
}
return decoded;
}
try {
const payload = verifyToken(token);
console.log('ユーザーID:', payload.userId);
} catch (err) {
if (err instanceof jwt.TokenExpiredError) {
console.error('トークンの有効期限が切れています');
} else if (err instanceof jwt.JsonWebTokenError) {
console.error('トークンが不正です:', err.message);
} else if (err instanceof jwt.NotBeforeError) {
console.error('トークンはまだ有効ではありません');
}
}
4. jwt.decode() — 検証なしのデコード
import jwt from 'jsonwebtoken';
// 署名を検証せずにペイロードを読み取る
// ⚠️ 信頼できないトークンの内容を信用してはいけない
const decoded = jwt.decode(token, { complete: true });
console.log(decoded);
// {
// header: { alg: 'HS256', typ: 'JWT' },
// payload: { userId: 1, iat: 1700000000, exp: 1700007200 },
// signature: 'xxxxx'
// }
// ペイロードだけ取得
const payload = jwt.decode(token);
console.log(payload);
// { userId: 1, iat: 1700000000, exp: 1700007200 }
5. Express ミドルウェアとしての実践的な使い方
import express, { Request, Response, NextFunction } from 'express';
import jwt, { JwtPayload } from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET!;
// カスタム型を拡張
interface AuthRequest extends Request {
user?: JwtPayload;
}
// 認証ミドルウェア
function authenticate(req: AuthRequest, res: Response, next: NextFunction): void {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
res.status(401).json({ error: 'Authorization header missing' });
return;
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, SECRET, {
algorithms: ['HS256'],
});
if (typeof decoded === 'string') {
res.status(401).json({ error: 'Invalid token format' });
return;
}
req.user = decoded;
next();
} catch (err) {
if (err instanceof jwt.TokenExpiredError) {
res.status(401).json({ error: 'Token expired' });
} else {
res.status(401).json({ error: 'Invalid token' });
}
}
}
const app = express();
// ログインエンドポイント
app.post('/login', (req: Request, res: Response) => {
// 認証ロジック(省略)
const token = jwt.sign(
{ userId: 1, role: 'admin' },
SECRET,
{ expiresIn: '1h' }
);
res.json({ token });
});
// 保護されたエンドポイント
app.get('/profile', authenticate, (req: AuthRequest, res: Response) => {
res.json({ user: req.user });
});
類似パッケージとの比較
| 特徴 | jsonwebtoken | jose | fast-jwt |
|---|---|---|---|
| 週間DL数 | 約1,800万 | 約1,000万 | 約100万 |
| TypeScript対応 | @types/jsonwebtoken が必要 | ネイティブ対応 | ネイティブ対応 |
| 非同期API | コールバック形式 | Promise / async-await | Promise / async-await |
| Web Crypto API対応 | ❌(Node.js専用) | ✅(ブラウザ・Edge Runtime対応) | ❌(Node.js専用) |
| パフォーマンス | 標準的 | 標準的 | 高速(キャッシュ機構あり) |
| JWE(暗号化)対応 | ❌ | ✅ | ❌ |
| メンテナンス | Auth0が管理、安定 | 活発 | 活発 |
補足: Next.js の Edge Runtime や Cloudflare Workers で JWT を扱う場合は、Web Crypto API に対応した jose が適しています。Node.js 専用のバックエンドであれば jsonwebtoken で十分です。
注意点・Tips
🔴 jwt.decode() を認証に使わない
decode() は署名を検証しません。認証・認可の判断には必ず verify() を使ってください。
// ❌ 危険: 改ざんされたトークンでも読めてしまう
const payload = jwt.decode(untrustedToken);
// ✅ 安全: 署名が不正なら例外がスローされる
const payload = jwt.verify(untrustedToken, SECRET);
🔴 algorithms オプションを必ず指定する
verify() で algorithms を省略すると、デフォルトのアルゴリズムリストが使われます。攻撃者がヘッダーのアルゴリズムを書き換える「Algorithm Confusion Attack」を防ぐため、許可するアルゴリズムを明示しましょう。
// ✅ 許可するアルゴリズムを明示
jwt.verify(token, SECRET, { algorithms: ['HS256'] });
🟡 expiresIn の文字列指定に注意
数値は「秒」として解釈されますが、単位なしの文字列は「ミリ秒」として解釈されます。
jwt.sign(payload, SECRET, { expiresIn: 60 }); // 60秒
jwt.sign(payload, SECRET, { expiresIn: '60' }); // 60ミリ秒 ⚠️
jwt.sign(payload, SECRET, { expiresIn: '60s' }); // 60秒 ✅
jwt.sign(payload, SECRET, { expiresIn: '1h' }); // 1時間 ✅
jwt.sign(payload, SECRET, { expiresIn: '7d' }); // 7日 ✅
🟡 シークレットキーは十分な長さにする
HMAC SHA256 の場合、最低でも256ビット(32バイト)以上のランダムな文字列を使いましょう。
# 安全なシークレットの生成例
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
🟡 ペイロードに機密情報を入れない
JWT のペイロードは Base64URL エンコードされているだけで、暗号化されていません。パスワードやクレジットカード番号などの機密情報は絶対に含めないでください。
🟢 exp と expiresIn を同時に指定しない
ペイロードに直接 exp を含める方法と、オプションの expiresIn を使う方法がありますが、両方を同時に指定するとエラーになります。
// ❌ エラー: 両方指定はできない
jwt.sign({ exp: Math.floor(Date.now() / 1000) + 3600 }, SECRET, { expiresIn: '1h' });
// ✅ どちらか一方を使う
jwt.sign({ data: 'test' }, SECRET, { expiresIn: '1h' });
まとめ
jsonwebtoken は Node.js における JWT 実装のデファクトスタンダードであり、sign() / verify() / decode() の3つの API だけでトークンベース認証を実装できるシンプルさが魅力です。algorithms の明示や decode() と verify() の使い分けなど、セキュリティ上の注意点を押さえておけば、安全で堅牢な認証基盤を構築できます。Edge Runtime やブラウザ環境が必要な場合は jose への移行も検討してください。