Stack Horizon

JWTの実際とは

JWT(JSON Web Token)とは、クレームを2者間で送信するためのコンパクトでURLセーフな方法です。魔法のようなセキュリティ解決策ではありません。トークン形式です:ヘッダー、ペイロード、署名の3つのbase64urlエンコードされたセグメントがドットで区切られています。

header: {"alg":"HS256","typ":"JWT"}
payload: {"sub":"user123","iat":1700000000}
signature: HMACSHA256(base64url(header)+"."+base64url(payload), secret)

Enter fullscreen mode Exit fullscreen mode

よくある間違い:localStorageにJWTを保存する

やってはいけません。localStorageはJavaScript(XSS)からアクセス可能です。攻撃者がスクリプトを注入すると、トークンが盗まれます。代わりにhttpOnlyクッキーを使用してください。クッキーはリクエストに自動的に送信され、JSから読み取れません。

// Bad: vulnerable to XSS
localStorage.setItem('token', jwt);

// Better: httpOnly cookie (set by server)
Set-Cookie: token=<jwt>; HttpOnly; Secure; SameSite=Strict

Enter fullscreen mode Exit fullscreen mode

JWT認証の実装方法(実践的アプローチ)

1. ログインエンドポイントがクッキーでJWTを発行

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const user = authenticate(username, password);
  if (!user) return res.status(401).send('Invalid credentials');

  const token = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '15m' }
  );

  res.cookie('token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 15 * 60 * 1000
  });
  res.json({ message: 'Logged in' });
});

Enter fullscreen mode Exit fullscreen mode

2. 各リクエストでトークンを検証するミドルウェア

function authMiddleware(req, res, next) {
  const token = req.cookies.token;
  if (!token) return res.status(401).send('No token');

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(403).send('Invalid token');
  }
}

app.get('/protected', authMiddleware, (req, res) => {
  res.json({ user: req.user });
});

Enter fullscreen mode Exit fullscreen mode

3. 長時間セッションのためのリフレッシュトークン

アクセストークンは短命(15分)です。リフレッシュトークン(長命で、別のhttpOnlyクッキーに保存)を使用して、再度認証情報を求めずに新しいアクセストークンを取得します。

app.post('/refresh', (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  if (!refreshToken) return res.status(401).send('No refresh token');

  try {
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
    const newAccessToken = jwt.sign(
      { sub: decoded.sub },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );
    res.cookie('token', newAccessToken, { httpOnly: true, secure: true, sameSite: 'strict' });
    res.json({ message: 'Token refreshed' });
  } catch (err) {
    return res.status(403).send('Invalid refresh token');
  }
});

Enter fullscreen mode Exit fullscreen mode

JWTが解決しないこと

  • CSRF:クッキーを使用する場合、CSRF対策が必要です。SameSite=StrictまたはCSRFトークンを使用してください。
  • トークンの失効:JWTはステートレスです。ブラックリストを維持しない限り、個別のトークンを失効できません(ステートレス性を損ないます)。ログアウトの場合は、サーバー側でクッキーをクリアするか、短い有効期限を使用してください。
  • ペイロードの改ざん:署名により整合性が保証されます。署名を検証せずにペイロードを信頼してはいけません。

よくある落とし穴

  • コードにシークレットを保存する:環境変数を使用してください。
  • 弱いシークレットを使用する:長くランダムな文字列を使用してください(例:openssl rand -hex 64)。
  • アルゴリズムの検証をしない:サーバーが「none」アルゴリズムを受け入れる場合、攻撃者がトークンを偽造できます。許可されたアルゴリズムを必ず指定してください。
// Explicitly set algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });

Enter fullscreen mode Exit fullscreen mode

まとめ

JWTはツールであり、銀の弾丸ではありません。httpOnlyクッキー、短命トークン、リフレッシュトークン、適切なCSRF対策を使用してください。トレードオフを理解しましょう:ステートレス性 vs. 失効。シンプルに保ちましょう。