Stack Horizon

JWT 究竟是什麼

JWT(JSON Web Token)是一種精簡且 URL 安全的傳遞聲明(claims)的方式。它不是神奇的安全解決方案,而是一種 token 格式:由三個以點(.)分隔的 base64url 編碼片段組成(header.payload.signature)。

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

Enter fullscreen mode Exit fullscreen mode

常見錯誤:把 JWT 存放在 localStorage

不要這麼做。localStorage 可透過 JavaScript 存取(XSS)。如果攻擊者注入腳本,就能竊取你的 token。請改用 httpOnly cookie。Cookie 會隨請求自動傳送,且無法被 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. 登入端點以 Cookie 發出 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. 中介軟體在每次請求時驗證 token

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. 使用 refresh token 延長會話

Access token 存活時間很短(15 分鐘)。請使用 refresh token(存活時間較長,存放在另一個 httpOnly cookie 中),在不重新輸入憑證的情況下取得新的 access token。

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:若使用 cookie,需搭配 CSRF 保護。建議使用 SameSite=Strict 或 CSRF token。
  • Token 撤銷:JWT 是無狀態的。除非維護黑名單(這會破壞無狀態特性),否則無法撤銷單一 token。登出時,請在伺服器端清除 cookie 或使用較短的過期時間。
  • Payload 竄改:簽章可確保完整性。未驗證簽章前,請勿信任 payload。

常見陷阱

  • 將金鑰寫死在程式碼中:請使用環境變數。
  • 使用弱金鑰:請使用長且隨機的字串(例如 openssl rand -hex 64)。
  • 未驗證演算法:若伺服器接受 'none' 演算法,攻擊者即可偽造 token。請務必指定允許的演算法。
// Explicitly set algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });

Enter fullscreen mode Exit fullscreen mode

摘要

JWT 是一種工具,而非萬靈丹。請使用 httpOnly cookie、短效 token、refresh token,並妥善實作 CSRF 防護。理解無狀態性與撤銷之間的取捨,保持簡單即可。