mcpField
J
jwt

jwt

github.com/claude-dev-suiteセキュリティ4637 回インストール2 日前に更新
リポジトリ

jwt について

Node.js アプリケーションで JWT 認証を実装し、安全に運用するためのリファレンススキルです。トークンの検証やローテーション、失効処理、本番環境のセキュリティ指針を扱います。

主な機能

  1. 01JWT の構造とクレーム
  2. 02Node.js のトークン生成とミドルウェア
  3. 03安全な保存方法とアルゴリズムの指針
  4. 04リフレッシュトークンのローテーションと失効
  5. 05トークン関連の一般的な問題の調査

活用シーン

  1. 01Bearer トークン認証の構築
  2. 02JWT の保存方法と有効期限の検討
  3. 03リフレッシュトークンのローテーションと失効処理の追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill jwt

インストール後、Claude Code で/jwtを入力して読み込まれたか確認します。

同梱ファイルquick-ref/implementation.mdquick-ref/security.md

詳細

JWT コア知識

深い知識:包括的なドキュメントを参照するには、technology: jwt を指定して mcp__documentation__fetch_docs を使用してください。

トークンの構造

header.payload.signature

Header: { "alg": "HS256", "typ": "JWT" }
Payload: { "sub": "1234", "name": "John", "iat": 1516239022 }
Signature: HMACSHA256(base64(header) + "." + base64(payload), secret)

Node.js の実装

import jwt from 'jsonwebtoken';

const SECRET = process.env.JWT_SECRET!;

// Generate token
function generateToken(user: User): string {
  return jwt.sign(
    { sub: user.id, email: user.email },
    SECRET,
    { expiresIn: '1h' }
  );
}

// Verify token
function verifyToken(token: string): JwtPayload {
  return jwt.verify(token, SECRET) as JwtPayload;
}

// Refresh token pattern
function generateRefreshToken(user: User): string {
  return jwt.sign(
    { sub: user.id, type: 'refresh' },
    SECRET,
    { expiresIn: '7d' }
  );
}

ミドルウェア

const authenticate = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.split(' ')[1];
  try {
    req.user = verifyToken(token);
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

このスキルを使用しない場合

  • セッションベース認証 - Cookie を使用した従来のサーバーサイドセッションを使用してください
  • OAuth 2.0 フロー - サードパーティ認証には oauth2 スキルを使用してください
  • NextAuth.js - Next.js 認証には nextauth スキルを使用してください
  • シンプルな内部 API - API キーで十分な場合があります

ベストプラクティス

推奨事項禁止事項
HTTPS を使用するlocalStorage に保存する(httpOnly Cookie を使用してください)
有効期限を短くする(15m~1h)ペイロードに機密データを含める
すべてのクレームを検証する脆弱なシークレットを使用する
リフレッシュトークンを使用する有効期限を無視する

アンチパターン

アンチパターン問題点正しい方法
JWT を localStorage に保存するXSS 攻撃に対して脆弱ですhttpOnly Cookie を使用する
有効期間の長いアクセストークン漏えいした場合にセキュリティリスクがあります有効期限を15分にし、リフレッシュトークンを使用する
脆弱なシークレット(< 32 bytes)ブルートフォース攻撃で簡単に破られます256-bit のランダムなシークレットを使用する
アルゴリズムの検証を無視するアルゴリズム混同攻撃が発生する可能性があります許可するアルゴリズムを明示的に指定する
ペイロードにパスワードを含めるトークンは base64 エンコードされているだけで、暗号化されていません機密性のないクレームのみを含める
トークン失効の仕組みがないユーザーがログアウトできませんブラックリストまたはトークンのバージョン管理を実装する

簡単なトラブルシューティング

問題原因解決策
"Invalid signature"シークレットまたはアルゴリズムが誤っていますJWT_SECRET が一致していることを確認し、アルゴリズムを確認してください
"Token expired"exp クレームの日時が過去ですリフレッシュトークンのフローを実装してください
"Missing token"Authorization ヘッダーが送信されていませんAuthorization: Bearer <token> を確認してください
トークンが認識されないトークンの形式が不正ですheader.payload.signature の形式を確認してください
Cookie に関する CORS エラーSameSite/Secure フラグの設定に問題がありますsameSite:'strict', secure:true を設定してください
ログアウトが機能しないトークンがステートレスですRedis/DB を使用した失効処理を実装してください

標準クレーム

クレーム目的
subサブジェクト(ユーザー ID)
iat発行日時
exp有効期限
iss発行者
aud対象者

本番環境への対応

セキュリティ設定
// Use asymmetric keys (RS256) for production
import * as jose from 'jose';

// Generate key pair (run once, store securely)
// openssl genrsa -out private.pem 2048
// openssl rsa -in private.pem -pubout -out public.pem

const privateKey = await jose.importPKCS8(
  process.env.JWT_PRIVATE_KEY!,
  'RS256'
);
const publicKey = await jose.importSPKI(
  process.env.JWT_PUBLIC_KEY!,
  'RS256'
);

// Sign token
async function generateToken(user: User): Promise<string> {
  return new jose.SignJWT({
    sub: user.id,
    email: user.email,
  })
    .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
    .setIssuedAt()
    .setIssuer(process.env.JWT_ISSUER!)
    .setAudience(process.env.JWT_AUDIENCE!)
    .setExpirationTime('15m')  // Short-lived access token
    .sign(privateKey);
}

// Verify token
async function verifyToken(token: string): Promise<jose.JWTPayload> {
  const { payload } = await jose.jwtVerify(token, publicKey, {
    issuer: process.env.JWT_ISSUER!,
    audience: process.env.JWT_AUDIENCE!,
  });
  return payload;
}
安全なトークンの保存
// Server-side: HttpOnly cookie for access token
res.cookie('access_token', token, {
  httpOnly: true,     // Prevents XSS access
  secure: true,       // HTTPS only
  sameSite: 'strict', // CSRF protection
  maxAge: 15 * 60 * 1000, // 15 minutes
  path: '/',
});

// Refresh token in separate cookie
res.cookie('refresh_token', refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
  path: '/api/auth/refresh', // Only sent to refresh endpoint
});
トークンのローテーションと失効
// Refresh token rotation
async function refreshTokens(refreshToken: string) {
  // Verify refresh token
  const payload = await verifyRefreshToken(refreshToken);

  // Check if refresh token is in blacklist (revoked)
  if (await isTokenRevoked(refreshToken)) {
    throw new Error('Token revoked');
  }

  // Revoke old refresh token
  await revokeToken(refreshToken);

  // Generate new tokens
  const user = await db.users.findUnique({ where: { id: payload.sub } });
  return {
    accessToken: await generateToken(user),
    refreshToken: await generateRefreshToken(user),
  };
}

// Token revocation with Redis
async function revokeToken(token: string): Promise<void> {
  const payload = await jose.decodeJwt(token);
  const ttl = payload.exp! - Math.floor(Date.now() / 1000);
  if (ttl > 0) {
    await redis.set(`revoked:${token}`, '1', 'EX', ttl);
  }
}

// Logout: revoke all user tokens
async function logoutAll(userId: string): Promise<void> {
  // Increment user's token version, invalidating all existing tokens
  await db.users.update({
    where: { id: userId },
    data: { tokenVersion: { increment: 1 } },
  });
}
アルゴリズムのセキュリティ
// NEVER allow 'none' algorithm
// ALWAYS specify allowed algorithms explicitly
const { payload } = await jose.jwtVerify(token, publicKey, {
  algorithms: ['RS256'], // Only allow RS256
  issuer: process.env.JWT_ISSUER!,
  audience: process.env.JWT_AUDIENCE!,
});

// Validate token type to prevent token confusion
if (payload.type !== 'access') {
  throw new Error('Invalid token type');
}
監視メトリクス
メトリクスアラートのしきい値
トークン検証の失敗> 100/min
リフレッシュトークンの再利用試行> 10/min
有効期限切れトークンのリクエスト> 500/min
署名の無効エラー> 50/min
クレームの検証
async function validateTokenClaims(payload: jose.JWTPayload): Promise<void> {
  // Check required claims
  if (!payload.sub || !payload.iat || !payload.exp) {
    throw new Error('Missing required claims');
  }

  // Check user still exists and is active
  const user = await db.users.findUnique({ where: { id: payload.sub } });
  if (!user || !user.isActive) {
    throw new Error('User not found or inactive');
  }

  // Check token version (for logout-all functionality)
  if (payload.tokenVersion !== user.tokenVersion) {
    throw new Error('Token invalidated');
  }
}
チェックリスト
  • 本番環境では RS256(非対称鍵)を使用する
  • アクセストークンの有効期限を短くする(15分)
  • ローテーション付きのリフレッシュトークンを使用する
  • HttpOnly Cookie を使用する(localStorage は使用しない)
  • Cookie に Secure と SameSite のフラグを設定する
  • トークン失効の仕組みを用意する
  • 発行者と対象者を検証する
  • 許可するアルゴリズムを明示的に指定する
  • 全デバイスからログアウトするためにトークンバージョンを含める
  • トークン検証の失敗を監視する
  • トークンエンドポイントにレート制限を適用する

参考ドキュメント

コメント