# Token Revocation

This page explains what `RevokeJWT()` does at logout, how the blacklist takes effect, and when nothing gets revoked.

## Revocation Flow

| Step | Action |
|---|---|
| 1 | Resolve the Refresh ID: `refresh_id` argument → `X-Refresh-ID` header → refresh cookie; return immediately if none |
| 2 | Clear the access and refresh cookies |
| 3 | Read `refresh:<id>`; stop if it does not exist |
| 4 | Shorten the TTL of `refresh:<id>` to 5 seconds |
| 5 | Write `revoke:<access_token>` with value `"1"` and TTL `accessTokenExpires` |

## How the Blacklist Takes Effect

The first thing `VerifyJWT()` does is look up `revoke:<access_token>`; on a hit it returns a guest (`isError: false`) without checking the signature. The blacklist TTL equals the Access Token lifetime, so the entry disappears once the token would have expired anyway.

## When Nothing Is Revoked

| Situation | Result |
|---|---|
| The request carries no Refresh ID | Returns immediately: cookies are not cleared and nothing is blacklisted |
| The refresh data is already gone from Redis | Cookies are cleared, but the Access Token is not blacklisted and keeps passing verification until it expires |
| Redis error | Logged with `console.error`, not thrown |

To guarantee revocation, clients should send both the Access Token and the Refresh ID.

## Example

```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) {
    // Throws only when not initialized
    res.status(500).json({ error: (err as Error).message });
  }
}
```

Related: [Redis Keys](/redis-keys), [Verification Results](/verify-results)
