# Token 撤銷

本頁說明 `RevokeJWT()` 登出時做了什麼、黑名單如何生效，以及不會撤銷的情況。

## 撤銷流程

| 步驟 | 動作 |
|---|---|
| 1 | 取得 Refresh ID：參數 `refresh_id` → `X-Refresh-ID` header → Refresh cookie；都沒有則直接返回 |
| 2 | 清除 Access 與 Refresh 兩個 cookie |
| 3 | 讀取 `refresh:<id>`；不存在則停止 |
| 4 | 將 `refresh:<id>` 的 TTL 縮為 5 秒 |
| 5 | 寫入 `revoke:<access_token>`，值 `"1"`，TTL 為 `accessTokenExpires` |

## 黑名單如何生效

`VerifyJWT()` 第一步檢查 `revoke:<access_token>`，命中即回傳訪客（`isError: false`），不再驗證簽章。黑名單 TTL 等於 Access Token 壽命，Token 自然過期後紀錄也一併清除。

## 不會撤銷的情況

| 情況 | 結果 |
|---|---|
| 請求沒有任何 Refresh ID | 直接返回：cookie 不清除、黑名單不寫入 |
| Redis 中的 Refresh 資料已不存在 | cookie 已清除，但 Access Token 不進黑名單，在過期前仍可通過驗證 |
| Redis 錯誤 | 以 `console.error` 記錄，不拋出 |

需要確保撤銷時，客戶端應同時送出 Access Token 與 Refresh ID。

## 範例

```typescript
import { Request, Response } from "express";
import { JWTAuth } from "@pardnchiu/jwt-auth";

export async function logout(req: Request, res: Response) {
  try {
    await JWTAuth.RevokeJWT(req, res);
    res.json({ message: "Successfully logged out" });
  } catch (err) {
    // 僅在未初始化時拋出
    res.status(500).json({ error: (err as Error).message });
  }
}
```

相關：[Redis 鍵](/zh/redis-keys)、[驗證結果](/zh/verify-results)
