jsonwebtoken (Node.js)
ライブラリ
jsonwebtoken (Node.js)
概要
jsonwebtokenは、Auth0が開発・保守するNode.js向けのJSON Web Token(JWT)ライブラリです。2025年現在、npm上で週間ダウンロード数が2800万回を超える最も人気の高いJWTライブラリの一つとなっており、34,000以上のプロジェクトで使用されています。RFC 7519準拠のJWT実装として、対称・非対称両方の署名アルゴリズムをサポートし、HS256、RS256、ES256等の主要アルゴリズムに完全対応しています。トークンの生成(jwt.sign)、検証(jwt.verify)、デコード(jwt.decode)の基本機能に加え、有効期限設定、カスタムクレーム、非同期処理サポートを提供します。Express.js、Hapi、Koa等のWebフレームワークとの統合が容易で、認証・認可システムの構築において業界標準として広く採用されています。
詳細
jsonwebtoken 9.x系は、Node.js 12以上をサポートし、最新のJavaScript(ES2020+)機能を活用した高性能なJWT処理を提供します。ライブラリの核心機能として、Promiseベースの非同期API、Callbackベースの従来API、同期処理APIの3つのインターフェースを提供し、多様な開発スタイルに対応します。セキュリティ面では、HMAC-SHA256(HS256)、RSA-SHA256(RS256)、ECDSA-SHA256(ES256)を含む幅広い署名アルゴリズムをサポートし、鍵管理とローテーションにも対応しています。パフォーマンス最適化により、大量のトークン処理においても高速動作を実現し、エンタープライズレベルのアプリケーションにも適用可能です。
主な特徴
- 包括的JWT実装: RFC 7519完全準拠のトークン生成・検証・デコード機能
- 多様な署名アルゴリズム: HS256/HS384/HS512、RS256/RS384/RS512、ES256/ES384/ES512対応
- 柔軟なAPI設計: 同期・非同期・Promiseベースの3つのインターフェース提供
- 高度なセキュリティ: 有効期限、発行者、対象者検証とカスタムクレーム対応
- 優れた互換性: 主要Node.jsフレームワークとの統合実績
- エンタープライズ対応: 大規模環境での高いパフォーマンスと信頼性
メリット・デメリット
メリット
- Auth0による長期間の保守実績で高い信頼性と安定性を提供
- npm週間ダウンロード数2800万回超の豊富な導入実績と情報量
- RFC 7519完全準拠により他のJWT実装との完全な互換性を保証
- 同期・非同期・Promiseの3つのAPIパターンで多様な開発スタイルに対応
- HS256からES512まで幅広い署名アルゴリズムサポートでセキュリティ要件に柔軟対応
- Express.js等のWebフレームワークとの統合が容易で開発効率が高い
デメリット
- Node.js専用ライブラリでブラウザー環境では直接使用不可
- 大量のトークン検証時にCPU使用率が高くなる場合がある
- 非対称署名(RS256等)使用時は鍵管理の複雑性が増加
- TypeScript型定義が別パッケージ(@types/jsonwebtoken)で提供
- セキュリティ設定の不備により脆弱性を生む可能性がある
- 複雑なクレーム検証ロジックには追加実装が必要
参考ページ
書き方の例
基本的なセットアップと依存関係のインストール
# jsonwebtokenライブラリのインストール
npm install jsonwebtoken
# TypeScript使用時の型定義インストール
npm install --save-dev @types/jsonwebtoken
# セキュリティ強化のための追加パッケージ(オプション)
npm install crypto
npm install dotenv # 環境変数管理用
// 基本的なrequire文(CommonJS)
const jwt = require('jsonwebtoken');
// ES6 import文(モジュール使用時)
import jwt from 'jsonwebtoken';
// TypeScript環境での型付きimport
import * as jwt from 'jsonwebtoken';
// package.json設定例
{
"name": "jwt-auth-app",
"version": "1.0.0",
"dependencies": {
"jsonwebtoken": "^9.0.2",
"express": "^4.18.2",
"dotenv": "^16.3.1"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.3",
"@types/node": "^20.5.0"
}
}
JWT トークンの生成とカスタムクレーム設定
// JWTTokenService.js - トークン生成サービス
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
class JWTTokenService {
constructor() {
// 環境変数からシークレットキーを取得(本番環境では必須)
this.secretKey = process.env.JWT_SECRET || this.generateSecretKey();
this.issuer = process.env.JWT_ISSUER || 'your-app-name';
this.audience = process.env.JWT_AUDIENCE || 'your-app-users';
}
// セキュアなシークレットキー生成
generateSecretKey() {
return crypto.randomBytes(64).toString('hex');
}
// 基本的なJWTトークン生成
generateToken(payload, options = {}) {
// デフォルトオプション設定
const defaultOptions = {
expiresIn: '1h', // 1時間有効
issuer: this.issuer, // 発行者
audience: this.audience, // 対象者
algorithm: 'HS256' // 署名アルゴリズム
};
const finalOptions = { ...defaultOptions, ...options };
try {
// 同期でトークン生成
const token = jwt.sign(payload, this.secretKey, finalOptions);
console.log('JWT token generated successfully');
return token;
} catch (error) {
console.error('JWT token generation failed:', error.message);
throw new Error('Token generation failed');
}
}
// 非同期でトークン生成(Callback版)
generateTokenAsync(payload, options = {}, callback) {
const defaultOptions = {
expiresIn: '1h',
issuer: this.issuer,
audience: this.audience,
algorithm: 'HS256'
};
const finalOptions = { ...defaultOptions, ...options };
jwt.sign(payload, this.secretKey, finalOptions, (error, token) => {
if (error) {
console.error('Async JWT token generation failed:', error.message);
return callback(error, null);
}
console.log('Async JWT token generated successfully');
callback(null, token);
});
}
// Promise版非同期トークン生成
generateTokenPromise(payload, options = {}) {
return new Promise((resolve, reject) => {
this.generateTokenAsync(payload, options, (error, token) => {
if (error) {
reject(error);
} else {
resolve(token);
}
});
});
}
// ユーザー認証用トークン生成
generateUserToken(user) {
const payload = {
sub: user.id, // Subject(ユーザーID)
username: user.username,
email: user.email,
roles: user.roles || ['user'],
permissions: user.permissions || [],
iat: Math.floor(Date.now() / 1000) // 発行時刻
};
return this.generateToken(payload, {
expiresIn: '2h',
subject: user.id
});
}
// リフレッシュトークン生成
generateRefreshToken(userId) {
const payload = {
sub: userId,
type: 'refresh',
iat: Math.floor(Date.now() / 1000)
};
return this.generateToken(payload, {
expiresIn: '7d', // 7日間有効
subject: userId
});
}
// 管理者専用トークン生成
generateAdminToken(admin) {
const payload = {
sub: admin.id,
username: admin.username,
email: admin.email,
roles: ['admin'],
permissions: admin.permissions || ['read', 'write', 'delete'],
isAdmin: true,
department: admin.department,
iat: Math.floor(Date.now() / 1000)
};
return this.generateToken(payload, {
expiresIn: '1h', // 管理者トークンは短い有効期限
audience: this.audience + '-admin'
});
}
// 一時アクセストークン生成(パスワードリセット等)
generateTemporaryToken(userId, purpose) {
const payload = {
sub: userId,
purpose: purpose, // 'password-reset', 'email-verify' 等
temp: true,
iat: Math.floor(Date.now() / 1000)
};
return this.generateToken(payload, {
expiresIn: '15m', // 15分間のみ有効
audience: this.audience + '-temp'
});
}
}
// 使用例
const tokenService = new JWTTokenService();
// ユーザー情報
const user = {
id: '12345',
username: 'johndoe',
email: '[email protected]',
roles: ['user', 'premium'],
permissions: ['read', 'write']
};
// 各種トークン生成例
const userToken = tokenService.generateUserToken(user);
const refreshToken = tokenService.generateRefreshToken(user.id);
const resetToken = tokenService.generateTemporaryToken(user.id, 'password-reset');
console.log('User Token:', userToken);
console.log('Refresh Token:', refreshToken);
console.log('Reset Token:', resetToken);
module.exports = JWTTokenService;
JWT トークンの検証とセキュリティチェック
// JWTVerificationService.js - トークン検証サービス
const jwt = require('jsonwebtoken');
class JWTVerificationService {
constructor(secretKey, options = {}) {
this.secretKey = secretKey || process.env.JWT_SECRET;
this.issuer = options.issuer || process.env.JWT_ISSUER;
this.audience = options.audience || process.env.JWT_AUDIENCE;
this.algorithms = options.algorithms || ['HS256'];
}
// 基本的なトークン検証
verifyToken(token, options = {}) {
const defaultOptions = {
issuer: this.issuer,
audience: this.audience,
algorithms: this.algorithms,
clockTolerance: 30, // 30秒のクロック許容時間
ignoreExpiration: false,
ignoreNotBefore: false
};
const finalOptions = { ...defaultOptions, ...options };
try {
// 同期でトークン検証
const decoded = jwt.verify(token, this.secretKey, finalOptions);
console.log('JWT token verified successfully');
return {
success: true,
payload: decoded,
error: null
};
} catch (error) {
console.error('JWT token verification failed:', error.message);
return {
success: false,
payload: null,
error: this.categorizeError(error)
};
}
}
// 非同期でトークン検証(Callback版)
verifyTokenAsync(token, options = {}, callback) {
const defaultOptions = {
issuer: this.issuer,
audience: this.audience,
algorithms: this.algorithms,
clockTolerance: 30
};
const finalOptions = { ...defaultOptions, ...options };
jwt.verify(token, this.secretKey, finalOptions, (error, decoded) => {
if (error) {
console.error('Async JWT token verification failed:', error.message);
return callback({
success: false,
payload: null,
error: this.categorizeError(error)
});
}
console.log('Async JWT token verified successfully');
callback({
success: true,
payload: decoded,
error: null
});
});
}
// Promise版非同期トークン検証
verifyTokenPromise(token, options = {}) {
return new Promise((resolve, reject) => {
this.verifyTokenAsync(token, options, (result) => {
if (result.success) {
resolve(result);
} else {
reject(result);
}
});
});
}
// エラー分類
categorizeError(error) {
if (error.name === 'TokenExpiredError') {
return {
type: 'EXPIRED',
message: 'Token has expired',
expiredAt: error.expiredAt
};
} else if (error.name === 'JsonWebTokenError') {
return {
type: 'INVALID',
message: 'Token is invalid',
details: error.message
};
} else if (error.name === 'NotBeforeError') {
return {
type: 'NOT_ACTIVE',
message: 'Token not active',
date: error.date
};
} else {
return {
type: 'UNKNOWN',
message: 'Unknown verification error',
details: error.message
};
}
}
// トークンのデコード(検証なし)
decodeToken(token, options = {}) {
const defaultOptions = {
complete: false, // true にするとヘッダー、ペイロード、署名を返す
json: true // JSON オブジェクトとして返す
};
const finalOptions = { ...defaultOptions, ...options };
try {
const decoded = jwt.decode(token, finalOptions);
return {
success: true,
payload: decoded,
error: null
};
} catch (error) {
return {
success: false,
payload: null,
error: error.message
};
}
}
// 管理者権限チェック
verifyAdminToken(token) {
const result = this.verifyToken(token, {
audience: this.audience + '-admin'
});
if (!result.success) {
return result;
}
// 管理者権限チェック
if (!result.payload.isAdmin || !result.payload.roles.includes('admin')) {
return {
success: false,
payload: null,
error: {
type: 'INSUFFICIENT_PRIVILEGES',
message: 'Admin privileges required'
}
};
}
return result;
}
// ユーザー権限チェック
verifyUserPermissions(token, requiredPermissions = []) {
const result = this.verifyToken(token);
if (!result.success) {
return result;
}
const userPermissions = result.payload.permissions || [];
const hasAllPermissions = requiredPermissions.every(permission =>
userPermissions.includes(permission)
);
if (!hasAllPermissions) {
return {
success: false,
payload: null,
error: {
type: 'INSUFFICIENT_PERMISSIONS',
message: 'Required permissions not found',
required: requiredPermissions,
current: userPermissions
}
};
}
return result;
}
// リフレッシュトークン検証
verifyRefreshToken(token) {
const result = this.verifyToken(token);
if (!result.success) {
return result;
}
// リフレッシュトークンのタイプチェック
if (result.payload.type !== 'refresh') {
return {
success: false,
payload: null,
error: {
type: 'INVALID_TOKEN_TYPE',
message: 'Not a refresh token'
}
};
}
return result;
}
// 一時トークン検証
verifyTemporaryToken(token, expectedPurpose) {
const result = this.verifyToken(token, {
audience: this.audience + '-temp'
});
if (!result.success) {
return result;
}
// 一時トークンかつ目的が一致するかチェック
if (!result.payload.temp || result.payload.purpose !== expectedPurpose) {
return {
success: false,
payload: null,
error: {
type: 'INVALID_PURPOSE',
message: 'Token purpose mismatch',
expected: expectedPurpose,
actual: result.payload.purpose
}
};
}
return result;
}
}
// 使用例
const verificationService = new JWTVerificationService(process.env.JWT_SECRET, {
issuer: 'your-app-name',
audience: 'your-app-users'
});
// トークン検証例
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
// 基本的な検証
const result = verificationService.verifyToken(token);
if (result.success) {
console.log('Token is valid:', result.payload);
} else {
console.log('Token verification failed:', result.error);
}
// 管理者権限チェック
const adminResult = verificationService.verifyAdminToken(token);
// 権限チェック
const permissionResult = verificationService.verifyUserPermissions(token, ['read', 'write']);
module.exports = JWTVerificationService;
Express.js ミドルウェア統合と認証フロー
// JWTMiddleware.js - Express.js用JWTミドルウェア
const jwt = require('jsonwebtoken');
const JWTVerificationService = require('./JWTVerificationService');
class JWTMiddleware {
constructor(secretKey, options = {}) {
this.verificationService = new JWTVerificationService(secretKey, options);
this.cookieName = options.cookieName || 'auth_token';
this.headerName = options.headerName || 'authorization';
}
// 基本的な認証ミドルウェア
authenticate() {
return (req, res, next) => {
const token = this.extractToken(req);
if (!token) {
return res.status(401).json({
error: 'Authentication required',
message: 'No token provided'
});
}
const result = this.verificationService.verifyToken(token);
if (!result.success) {
return res.status(401).json({
error: 'Invalid token',
message: result.error.message,
type: result.error.type
});
}
// ユーザー情報をリクエストに追加
req.user = result.payload;
req.token = token;
next();
};
}
// オプショナル認証ミドルウェア
optionalAuthenticate() {
return (req, res, next) => {
const token = this.extractToken(req);
if (!token) {
req.user = null;
return next();
}
const result = this.verificationService.verifyToken(token);
if (result.success) {
req.user = result.payload;
req.token = token;
} else {
req.user = null;
}
next();
};
}
// 管理者権限チェックミドルウェア
requireAdmin() {
return (req, res, next) => {
const token = this.extractToken(req);
if (!token) {
return res.status(401).json({
error: 'Authentication required',
message: 'Admin access requires token'
});
}
const result = this.verificationService.verifyAdminToken(token);
if (!result.success) {
return res.status(403).json({
error: 'Access denied',
message: result.error.message,
type: result.error.type
});
}
req.user = result.payload;
req.token = token;
next();
};
}
// 権限チェックミドルウェア
requirePermissions(permissions = []) {
return (req, res, next) => {
const token = this.extractToken(req);
if (!token) {
return res.status(401).json({
error: 'Authentication required',
message: 'Permission check requires token'
});
}
const result = this.verificationService.verifyUserPermissions(token, permissions);
if (!result.success) {
return res.status(403).json({
error: 'Insufficient permissions',
message: result.error.message,
required: result.error.required,
current: result.error.current
});
}
req.user = result.payload;
req.token = token;
next();
};
}
// ロールチェックミドルウェア
requireRoles(roles = []) {
return (req, res, next) => {
const token = this.extractToken(req);
if (!token) {
return res.status(401).json({
error: 'Authentication required'
});
}
const result = this.verificationService.verifyToken(token);
if (!result.success) {
return res.status(401).json({
error: 'Invalid token',
message: result.error.message
});
}
const userRoles = result.payload.roles || [];
const hasRequiredRole = roles.some(role => userRoles.includes(role));
if (!hasRequiredRole) {
return res.status(403).json({
error: 'Insufficient role',
message: 'Required role not found',
required: roles,
current: userRoles
});
}
req.user = result.payload;
req.token = token;
next();
};
}
// トークン抽出ロジック
extractToken(req) {
// Authorizationヘッダーからトークン抽出
const authHeader = req.headers[this.headerName];
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
// Cookieからトークン抽出
if (req.cookies && req.cookies[this.cookieName]) {
return req.cookies[this.cookieName];
}
// クエリパラメータからトークン抽出(非推奨だが緊急時用)
if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
}
// Express.jsアプリケーション統合例
const express = require('express');
const cookieParser = require('cookie-parser');
const JWTTokenService = require('./JWTTokenService');
const app = express();
const tokenService = new JWTTokenService();
const jwtMiddleware = new JWTMiddleware(process.env.JWT_SECRET, {
issuer: 'your-app-name',
audience: 'your-app-users'
});
// 基本ミドルウェア設定
app.use(express.json());
app.use(cookieParser());
// ログインエンドポイント
app.post('/auth/login', async (req, res) => {
try {
const { username, password } = req.body;
// 認証ロジック(実際の実装では外部認証システムを使用)
const user = await authenticateUser(username, password);
if (!user) {
return res.status(401).json({
error: 'Authentication failed',
message: 'Invalid credentials'
});
}
// トークン生成
const accessToken = tokenService.generateUserToken(user);
const refreshToken = tokenService.generateRefreshToken(user.id);
// Cookieにトークンを設定
res.cookie('auth_token', accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 2 * 60 * 60 * 1000 // 2時間
});
res.json({
message: 'Login successful',
user: {
id: user.id,
username: user.username,
email: user.email,
roles: user.roles
},
tokens: {
accessToken,
refreshToken,
expiresIn: 7200 // 2時間(秒)
}
});
} catch (error) {
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
// 保護されたルート例
app.get('/api/profile',
jwtMiddleware.authenticate(),
(req, res) => {
res.json({
message: 'Profile data',
user: req.user
});
}
);
// 管理者専用ルート例
app.get('/api/admin/users',
jwtMiddleware.requireAdmin(),
(req, res) => {
res.json({
message: 'Admin users data',
admin: req.user
});
}
);
// 権限チェックが必要なルート例
app.post('/api/posts',
jwtMiddleware.requirePermissions(['write']),
(req, res) => {
res.json({
message: 'Post created successfully',
user: req.user
});
}
);
// ロールチェックが必要なルート例
app.get('/api/reports',
jwtMiddleware.requireRoles(['premium', 'admin']),
(req, res) => {
res.json({
message: 'Reports data',
user: req.user
});
}
);
// トークンリフレッシュエンドポイント
app.post('/auth/refresh', (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({
error: 'Refresh token required'
});
}
const result = jwtMiddleware.verificationService.verifyRefreshToken(refreshToken);
if (!result.success) {
return res.status(401).json({
error: 'Invalid refresh token',
message: result.error.message
});
}
// 新しいアクセストークン生成
const newAccessToken = tokenService.generateToken({
sub: result.payload.sub
});
res.json({
accessToken: newAccessToken,
expiresIn: 3600
});
});
// ログアウトエンドポイント
app.post('/auth/logout',
jwtMiddleware.authenticate(),
(req, res) => {
// Cookieクリア
res.clearCookie('auth_token');
res.json({
message: 'Logout successful'
});
}
);
async function authenticateUser(username, password) {
// 実際の認証ロジック(データベース、LDAP等)
// ここではダミー実装
if (username === 'admin' && password === 'password') {
return {
id: '1',
username: 'admin',
email: '[email protected]',
roles: ['admin', 'user'],
permissions: ['read', 'write', 'delete']
};
}
return null;
}
module.exports = { JWTMiddleware, app };
非対称暗号化(RS256)とセキュリティ強化
// RSAJWTService.js - RSA鍵ペアを使用したJWTサービス
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
class RSAJWTService {
constructor(options = {}) {
this.algorithm = 'RS256';
this.issuer = options.issuer || process.env.JWT_ISSUER;
this.audience = options.audience || process.env.JWT_AUDIENCE;
// RSA鍵ペアの読み込み
this.loadKeyPair();
}
// RSA鍵ペアの読み込み
loadKeyPair() {
try {
// 秘密鍵と公開鍵のパス
const privateKeyPath = process.env.JWT_PRIVATE_KEY_PATH || path.join(__dirname, 'keys', 'private.pem');
const publicKeyPath = process.env.JWT_PUBLIC_KEY_PATH || path.join(__dirname, 'keys', 'public.pem');
// 鍵ファイルが存在しない場合は生成
if (!fs.existsSync(privateKeyPath) || !fs.existsSync(publicKeyPath)) {
this.generateKeyPair();
}
this.privateKey = fs.readFileSync(privateKeyPath, 'utf8');
this.publicKey = fs.readFileSync(publicKeyPath, 'utf8');
console.log('RSA key pair loaded successfully');
} catch (error) {
console.error('Failed to load RSA key pair:', error.message);
throw new Error('RSA key pair initialization failed');
}
}
// RSA鍵ペアの生成
generateKeyPair() {
const keysDir = path.join(__dirname, 'keys');
// keysディレクトリが存在しない場合は作成
if (!fs.existsSync(keysDir)) {
fs.mkdirSync(keysDir, { recursive: true });
}
// 2048ビットRSA鍵ペア生成
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
// 鍵ファイルを保存
fs.writeFileSync(path.join(keysDir, 'private.pem'), privateKey);
fs.writeFileSync(path.join(keysDir, 'public.pem'), publicKey);
console.log('New RSA key pair generated and saved');
}
// RSA署名でトークン生成
generateToken(payload, options = {}) {
const defaultOptions = {
algorithm: this.algorithm,
expiresIn: '1h',
issuer: this.issuer,
audience: this.audience,
keyid: 'rsa-key-1' // キーID(複数鍵管理時に有用)
};
const finalOptions = { ...defaultOptions, ...options };
try {
// RSA秘密鍵で署名
const token = jwt.sign(payload, this.privateKey, finalOptions);
console.log('RSA JWT token generated successfully');
return token;
} catch (error) {
console.error('RSA JWT token generation failed:', error.message);
throw new Error('RSA token generation failed');
}
}
// RSA公開鍵でトークン検証
verifyToken(token, options = {}) {
const defaultOptions = {
algorithms: [this.algorithm],
issuer: this.issuer,
audience: this.audience,
clockTolerance: 30
};
const finalOptions = { ...defaultOptions, ...options };
try {
// RSA公開鍵で検証
const decoded = jwt.verify(token, this.publicKey, finalOptions);
console.log('RSA JWT token verified successfully');
return {
success: true,
payload: decoded,
error: null
};
} catch (error) {
console.error('RSA JWT token verification failed:', error.message);
return {
success: false,
payload: null,
error: this.categorizeError(error)
};
}
}
// JWKSエンドポイント用公開鍵情報取得
getPublicKeyJWKS() {
const keyObject = crypto.createPublicKey(this.publicKey);
const jwk = keyObject.export({ format: 'jwk' });
return {
keys: [{
kty: jwk.kty,
use: 'sig',
kid: 'rsa-key-1',
alg: 'RS256',
n: jwk.n,
e: jwk.e
}]
};
}
// 高度なトークン生成(カスタムヘッダー付き)
generateAdvancedToken(payload, customHeaders = {}) {
const headers = {
typ: 'JWT',
alg: this.algorithm,
kid: 'rsa-key-1',
...customHeaders
};
const token = jwt.sign(payload, this.privateKey, {
algorithm: this.algorithm,
expiresIn: '1h',
issuer: this.issuer,
audience: this.audience,
header: headers
});
return token;
}
// 鍵ローテーション対応
rotateKeys() {
try {
// 現在の鍵をバックアップ
const backupDir = path.join(__dirname, 'keys', 'backup');
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true });
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
fs.copyFileSync(
path.join(__dirname, 'keys', 'private.pem'),
path.join(backupDir, `private-${timestamp}.pem`)
);
fs.copyFileSync(
path.join(__dirname, 'keys', 'public.pem'),
path.join(backupDir, `public-${timestamp}.pem`)
);
// 新しい鍵ペア生成
this.generateKeyPair();
this.loadKeyPair();
console.log('Key rotation completed successfully');
return true;
} catch (error) {
console.error('Key rotation failed:', error.message);
return false;
}
}
// セキュリティレポート生成
generateSecurityReport(tokens = []) {
const report = {
timestamp: new Date().toISOString(),
algorithm: this.algorithm,
keyStatus: 'active',
tokensAnalyzed: tokens.length,
validTokens: 0,
expiredTokens: 0,
invalidTokens: 0,
securityIssues: []
};
tokens.forEach(token => {
const result = this.verifyToken(token);
if (result.success) {
report.validTokens++;
// セキュリティチェック
const payload = result.payload;
if (!payload.exp || (payload.exp - payload.iat) > 86400) {
report.securityIssues.push('Long-lived token detected');
}
} else if (result.error.type === 'EXPIRED') {
report.expiredTokens++;
} else {
report.invalidTokens++;
}
});
return report;
}
categorizeError(error) {
if (error.name === 'TokenExpiredError') {
return {
type: 'EXPIRED',
message: 'Token has expired',
expiredAt: error.expiredAt
};
} else if (error.name === 'JsonWebTokenError') {
return {
type: 'INVALID',
message: 'Token is invalid',
details: error.message
};
} else if (error.name === 'NotBeforeError') {
return {
type: 'NOT_ACTIVE',
message: 'Token not active',
date: error.date
};
} else {
return {
type: 'UNKNOWN',
message: 'Unknown verification error',
details: error.message
};
}
}
}
// 使用例とExpress.js統合
const express = require('express');
const app = express();
const rsaJWTService = new RSAJWTService({
issuer: 'secure-app',
audience: 'secure-app-users'
});
app.use(express.json());
// JWKSエンドポイント(公開鍵配布用)
app.get('/.well-known/jwks.json', (req, res) => {
res.json(rsaJWTService.getPublicKeyJWKS());
});
// RSA署名トークン生成エンドポイント
app.post('/auth/rsa-login', (req, res) => {
const { username, password } = req.body;
// 認証処理(省略)
const user = { id: 1, username: 'testuser', roles: ['user'] };
const token = rsaJWTService.generateToken({
sub: user.id,
username: user.username,
roles: user.roles
});
res.json({
token,
algorithm: 'RS256',
message: 'RSA signed token generated'
});
});
// RSA検証ミドルウェア
const requireRSAAuth = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token required' });
}
const token = authHeader.substring(7);
const result = rsaJWTService.verifyToken(token);
if (!result.success) {
return res.status(401).json({
error: 'Invalid token',
details: result.error
});
}
req.user = result.payload;
next();
};
// 保護されたルート
app.get('/api/secure-data', requireRSAAuth, (req, res) => {
res.json({
message: 'Secure data accessed with RSA verification',
user: req.user
});
});
// 鍵ローテーション管理エンドポイント(管理者専用)
app.post('/admin/rotate-keys', requireRSAAuth, (req, res) => {
if (!req.user.roles.includes('admin')) {
return res.status(403).json({ error: 'Admin access required' });
}
const success = rsaJWTService.rotateKeys();
if (success) {
res.json({ message: 'Key rotation completed successfully' });
} else {
res.status(500).json({ error: 'Key rotation failed' });
}
});
module.exports = RSAJWTService;