How to Check JWT Expiry
Read the exp Claim Without a Library
The exp claim is a Unix timestamp in seconds. Compare it to the current time and you know immediately whether the token is still valid.
How to check JWT expiry: the exp claim in a JWT payload is a Unix timestamp in seconds — subtract the current time and you know exactly how many seconds remain. No library, no server call, and no secret key needed because reading claims doesn't require signature verification.
Where the Expiry Lives in a JWT
A JSON Web Token has three dot-separated segments: header.payload.signature. The payload — the middle segment — is Base64url-encoded JSON. It contains all the claims, including exp. Because it's encoding (not encryption), any decoder can read it without a key.
| Segment | Contents | Needs the secret? |
|---|---|---|
| header | Algorithm & token type (e.g. {"alg":"HS256","typ":"JWT"}) | No |
| payload | Claims including exp, sub, iat, custom data | No |
| signature | HMAC or RSA digest of header + payload | Yes — to verify |
To check expiry, you only need the payload segment. Split the token on ., take index 1, and Base64url-decode it.
The exp Claim Format — Seconds, Not Milliseconds
The JWT specification (RFC 7519) defines exp as a NumericDate — an integer count of seconds since the Unix epoch (1970-01-01T00:00:00Z). A typical value looks like 1746230400.
JavaScript's Date.now() returns milliseconds. If you compare exp directly to Date.now() without multiplying by 1000, a token that expires in 2026 will appear expired in 1973 — because 1,746,230,400 ms is only about 20 days after the epoch.
The correct comparison in JavaScript is always exp * 1000 > Date.now() or equivalently exp > Date.now() / 1000.
JavaScript: Check JWT Expiry Without a Library
All modern browsers and Node.js include everything you need. The approach is: split, decode the payload, parse JSON, compare timestamps.
// Decode a Base64url string to a UTF-8 string (browser + Node 16+)
function base64UrlDecode(str) {
// Convert Base64url → Base64, then decode
const base64 = str.replace(/-/g, '+').replace(/_/g, '/');
return atob(base64); // browser
// Node.js alternative:
// return Buffer.from(base64, 'base64').toString('utf8');
}
// Returns true if the token is expired (or unparseable)
function isExpired(token) {
try {
const payload = JSON.parse(base64UrlDecode(token.split('.')[1]));
if (!payload.exp) return false; // no exp claim → never expires by design
return payload.exp * 1000 < Date.now();
} catch {
return true; // malformed token — treat as expired
}
}
// Get seconds remaining (negative = already expired)
function secondsRemaining(token) {
const payload = JSON.parse(base64UrlDecode(token.split('.')[1]));
return payload.exp - Math.floor(Date.now() / 1000);
}
// Usage
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
console.log(isExpired(token)); // true / false
console.log(secondsRemaining(token)); // e.g. 3542
No npm install required. The only external dependency is atob(), which has been in every browser since 2012 and in Node.js since v16.
Python: Check JWT Expiry Without PyJWT
The standard library's base64 and json modules are sufficient. PyJWT (and its dependency on cryptography) is only needed if you also want signature verification.
import base64, json, time
def decode_payload(token: str) -> dict:
# Grab the payload segment (index 1)
payload_b64 = token.split('.')[1]
# Base64url → Base64: restore padding
padding = 4 - len(payload_b64) % 4
payload_b64 += '=' * (padding % 4)
payload_bytes = base64.urlsafe_b64decode(payload_b64)
return json.loads(payload_bytes)
def is_expired(token: str) -> bool:
payload = decode_payload(token)
exp = payload.get('exp')
if exp is None:
return False # no exp claim
return time.time() > exp
def seconds_remaining(token: str) -> float:
payload = decode_payload(token)
return payload['exp'] - time.time()
# Usage
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
print(is_expired(token)) # True / False
print(seconds_remaining(token)) # e.g. 3541.8
Note the padding fix: Base64url strips trailing = padding, so you must restore it before calling base64.urlsafe_b64decode() or Python will raise a binascii.Error.
Terminal / Bash: One-Liner to Read exp
Useful in CI pipelines, shell scripts, or when you just want a quick sanity-check without opening a browser.
# Print the full payload as pretty JSON
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool
# Print only the exp value
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['exp'])"
# Print human-readable expiry date
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null \
| python3 -c "import sys,json,datetime; d=json.load(sys.stdin); print(datetime.datetime.fromtimestamp(d['exp']))"
# Check if already expired (exit code 0 = valid, 1 = expired)
EXP=$(echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['exp'])")
[ $(date +%s) -lt $EXP ] && echo "Valid" || echo "Expired"
The base64 -d flag is --decode on Linux. On macOS use base64 -D (capital D). The 2>/dev/null suppresses the "invalid input" warning that some versions emit when Base64url padding is missing.
Common Mistakes When Checking JWT Expiry
Date.now() is milliseconds; exp is seconds. The fix is always exp * 1000 < Date.now() in JavaScript. In Python, time.time() returns seconds so no conversion is needed.
Client clocks can drift by tens of seconds. A token that your server considers valid for another 5 seconds may appear expired to a client running 10 seconds fast. Add a small leeway buffer (30–60 s) or always re-check expiry server-side before acting on sensitive operations.
Reading exp tells you when the token claims to expire — it does not confirm the token was legitimately issued by your server. An attacker can craft a token with any exp value. For any security decision, always verify the signature too.
iat is when the token was issued — not when it expires. A token's maximum age is determined by exp, not by iat + some assumed lifetime. Always read exp directly.
When You Do Need a Library
Reading exp without a library is fine for debugging or display purposes. Reach for a library when you need any of the following:
- Displaying token expiry in a UI
- Proactively refreshing a token before it expires
- Logging or debugging claim values
- CI scripts checking token freshness
- Signature verification (RS256, HS256, ES256…)
nbf(not-before) enforcementaud(audience) oriss(issuer) validation- Server-side auth middleware
For server-side authentication — where the result drives an access control decision — always verify the signature. Popular choices: jsonwebtoken (Node.js), PyJWT (Python), golang-jwt/jwt (Go).
Decode & Inspect JWT Expiry Instantly
Paste your token into the free JWT Decoder — it decodes the payload, converts exp to a human-readable date, and shows an expired/valid badge. No library, no upload, no account.
FAQ
What is the exp claim in a JWT? +
exp (Expiration Time) is a standard registered claim defined in RFC 7519. Its value is a Unix timestamp in whole seconds representing the moment after which the token must no longer be accepted. If the current time is greater than exp, the token is considered expired.
Is exp in seconds or milliseconds? +
Seconds. The JWT spec requires NumericDate values to be seconds since the Unix epoch. JavaScript's Date.now() returns milliseconds, so you must compare exp * 1000 to Date.now() — not exp directly. Python's time.time() returns seconds, so no conversion is needed there.
What is clock skew and how should I handle it? +
Clock skew is the difference between the system clocks on the machine checking the token and the server that issued it. A client running a few seconds ahead may treat a still-valid token as expired. The standard mitigation is a leeway window of 30–60 seconds on the checking side. For security-critical gates, always do a final check server-side with a synchronized clock.
Do I need a library just to check if a JWT is expired? +
No. Checking exp only requires Base64url-decoding the middle segment and parsing the JSON — a few lines in any language. Libraries like PyJWT or jsonwebtoken are needed when you also want to verify the signature, enforce aud or iss claims, or apply nbf (not-before) logic.