JWT jti Claim (JWT ID)
The JWT jti (JWT ID) claim is a unique token identifier for replay-attack prevention. Learn when jti is essential, how to set up a Redis jti blocklist, and what to use as the jti value.
The `jti` (JWT ID) claim is a unique identifier for this specific token instance: a string that must be unique across all tokens issued by a given issuer. It is defined in RFC 7519 §4.1.7 and is the foundation of replay-attack prevention and per-token revocation.
Format and values
A string, typically a UUIDv4 (`"jti": "a7f3c2e1-4b5d-4e9f-a8c1-2d3e4f5a6b7c"`). `crypto.randomUUID()` in Node.js 16+ generates UUIDv4 natively. The value must be statistically unique without coordination.
Validation rule
To prevent replay attacks, store each presented `jti` in a cache (Redis is typical) with a TTL equal to the token's remaining lifetime. On every authenticated request, check whether the `jti` is already in the cache; if so, reject as a replay. For per-token revocation (logout), add the revoked `jti` to a blocklist with the same TTL pattern. A `jti` in a token that you never check is a false sense of security: either set up the store check or omit `jti`.
Common mistakes
- Adding jti to tokens but never checking it server-side. A jti you don't check is security theater: it adds payload bytes without preventing replay.
- Using a sequential integer or a timestamp as jti. These are guessable. Use a UUIDv4 or equivalent 122+ bits of randomness.
- Storing the jti blocklist without a TTL. The blocklist grows forever. Always set TTL = remaining token lifetime so expired tokens drop out automatically.
Code examples
jti blocklist (Node.js: Redis)
// On logout or forced revocation
async function revokeToken(decoded) {
const remaining = decoded.exp - Math.floor(Date.now() / 1000);
if (remaining > 0) {
await redis.setex(`blocklist:${decoded.jti}`, remaining, "1");
}
}
// On every authenticated request
const isRevoked = await redis.exists(`blocklist:${decoded.jti}`);
if (isRevoked) throw new Error("Token revoked"); Issue with jti (Python: PyJWT)
import uuid
token = jwt.encode(
{"sub": "user_123", "jti": str(uuid.uuid4())},
secret,
algorithm="HS256",
) Frequently asked questions
-
What is the jti claim in a JWT?
jti (JWT ID) is an RFC 7519 registered claim: a unique identifier for a specific token instance. Its primary purpose is replay-attack prevention: once a token is used, store its jti in a cache with a TTL matching its exp, and reject any subsequent request carrying the same jti. jti is also the key that enables per-token revocation (a blocklist of revoked jti values).
-
When is the JWT jti claim essential vs optional?
jti is essential for tokens that should be valid for exactly one use: password reset, email verification, one-time access grants, payment confirmation, magic links. For these, set up the full jti store check on every use. For standard access tokens used repeatedly across API calls, jti adds cache infrastructure overhead without proportional security gain unless you are also building a token revocation system, in which case jti is the key that lets you revoke individual tokens before they expire.
-
What should I use as the JWT jti value?
A UUIDv4: 36 characters, statistically unique without coordination, universally supported. Use crypto.randomUUID() in Node.js 16+, uuid.uuid4() in Python, or java.util.UUID.randomUUID() in Java. Avoid sequential integers (guessable) and timestamps (predictable, collisions across requests in the same second). The value must be unique across all tokens issued by a given issuer.