{Inspector}

Developer Guide · 2026

How to Check If a JWT Is Expired: exp Claim, Libraries & Decoders

A JWT is expired when its exp claim — a Unix timestamp in seconds — is in the past. You can read that claim by hand, let jsonwebtoken, PyJWT, JJWT or .NET throw for you, or paste the token into a client-side decoder for an instant verdict. This guide shows all three ways, the epoch-to-date conversion, and leeway.

Benjamin Rotshtein

Written by Benjamin Rotshtein

Updated

How do you check whether a JWT has expired?

Decode the payload and compare the exp claim — a Unix timestamp in seconds — against the current time. If exp is in the past, the token is expired and every library will reject it. Add a small leeway window on both sides to absorb clock drift between the issuer and your servers.

Method 1: read the exp claim by hand

The payload is only Base64URL-encoded, never encrypted. Split the token on dots, decode the second segment, and read exp. Then compare it with the current Unix time. This needs no secret, no library and no server — the verdict is a single integer comparison.

// JavaScript — browser or Node
const [header, payload, signature] = token.split(".");
const claims = JSON.parse(
  Buffer.from(payload, "base64url").toString("utf8")
);
const expired = Math.floor(Date.now() / 1000) >= claims.exp;
console.log(expired ? "EXPIRED" : "valid");
# Python
import time, base64, json

claims = json.loads(base64.urlsafe_b64decode(
    token.split(".")[1] + "=="))
expired = int(time.time()) >= claims["exp"]
print("EXPIRED" if expired else "valid")

Two caveats: this tells you about expiry only — it does not prove the token is authentic. And if you don’t know whether the issuer applies leeway on its side, your local verdict may differ from theirs by a few seconds.

Method 2: let the library throw

Production code should not hand-roll the comparison. Every major library verifies the signature first, then checks exp (and often nbf / iat), and raises a dedicated exception:

// Node: jsonwebtoken
import jwt from "jsonwebtoken";
try {
  jwt.verify(token, secret);
} catch (err) {
  if (err instanceof jwt.TokenExpiredError) {
    console.log("expired at", err.expiredAt);
  }
}
# Python: PyJWT
import jwt
try:
    jwt.decode(token, key, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
    print("EXPIRED")
// Java: jjwt
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.ExpiredJwtException;

try {
  Claims claims = Jwts.parserBuilder()
      .setSigningKey(secret)            // HS256 key
      .build()
      .parseClaimsJws(token)
      .getBody();
} catch (ExpiredJwtException e) {
  System.out.println("EXPIRED at " + e.getClaims().getExpiration());
}
// C#: System.IdentityModel.Tokens.Jwt (.NET)
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;

var handler = new JwtSecurityTokenHandler();
var validation = new TokenValidationParameters
{
    ValidateLifetime = true,
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = new SymmetricSecurityKey(secret),
    ValidateAudience = false,
    ValidateIssuer = false
};
try
{
    handler.ValidateToken(token, validation, out _);
}
catch (SecurityTokenExpiredException e)
{
    Console.WriteLine("EXPIRED — token expired at " + e.Expires);
}

How the popular libraries behave

LibraryOn expiryLeeway optionNote
jsonwebtoken (Node)TokenExpiredError with expiredAtclockTolerance option (seconds)Verifies signature — needs the secret
PyJWT (Python)ExpiredSignatureErrorleeway argument (seconds)Verifies signature — needs the key
JJWT (Java)ExpiredJwtException with claimssetClockSkewSeconds()Verifies signature — needs the key
System.IdentityModel.Tokens.Jwt (C#)SecurityTokenExpiredExceptionClockSkew property (default 5 min)Verifies signature — needs the key
Browser decode onlyNever — reports the claimN/A (raw values shown)No key needed — claims are readable

Convert the exp epoch timestamp to a readable date

exp is a Unix timestamp in seconds — a bare integer like 1789452110. To see the exact moment a token dies as a normal date, multiply by 1000 (JavaScript) or use the standard library (Python, Java, .NET). This is the conversion every “JWT expiration date converter” and decoder performs under the hood:

// JavaScript
const date = new Date(payload.exp * 1000);   // seconds -> ms
console.log(date.toISOString());             // 2026-08-14T11:03:22.000Z
console.log(date.toLocaleString());          // local, human-readable
# Python
from datetime import datetime, timezone
print(datetime.fromtimestamp(claims["exp"], tz=timezone.utc).isoformat())

In Java, Instant.ofEpochSecond(exp) and in .NET, DateTimeOffset.FromUnixTimeSeconds(exp) give the same answer. Note the claim has no timezone of its own — it is an absolute instant, so the human date you see depends on your local timezone (14:03 in UTC+3 is 11:03 UTC).

Leeway: the variable that changes the verdict

Every validator compares against its own clock, and clocks drift. Leeway makes validation succeed when now <= exp + leeway. Without it, a token minted by a server 2 seconds ahead of yours can fail seconds before its true expiry. With 30-60 seconds of leeway, small skew is absorbed — but keep leeway well below the token lifetime, or a token that should be dead can be resurrected for the duration of the grace period. If you see a server rejecting tokens that your local check says are valid, the first thing to compare is the two clocks, not the code.

Method 3: use a browser decoder for instant answers

When you hold a token and just want to know — is it expired, and exactly when did it die — the fastest answer is a JWT decoder. It reads the exp claim, converts it to a human-readable date, and compares it with your local clock — all in the browser, with the token never leaving your machine. That turns a “401 session expired” into “this token died 4 minutes ago at 14:03,” which immediately tells you whether the client is reusing a stale token or the server is minting them too short.

Frequently asked questions

How do I check if a JWT is expired manually?

Decode the middle (payload) segment, find the exp claim — a Unix timestamp in seconds — and compare it with the current Unix time: expired = currentSeconds >= payload.exp. currentSeconds is Math.floor(Date.now()/1000) in JavaScript or int(time.time()) in Python. If the comparison is true the token is expired, regardless of what any library or dashboard claims.

Does decoding a JWT tell you if it is expired?

Yes, if the decoder compares exp against the current time — but note it only reads the claim, it cannot verify the signature. A decoder shows you the raw exp timestamp and whether it has passed, which is enough for an 'is it expired?' answer. A library that verifies (jsonwebtoken, PyJWT, JJWT) throws a specific expiration error when the token is both validly signed and past exp.

Why does my library say the token is expired when my clock says otherwise?

Almost always clock skew: the server issuing the token and the server validating it disagree by a few seconds, and exp is compared as whole seconds. Some libraries also reject tokens whose iat or nbf is in the future. The standard fix is leeway — a tolerance window (typically 30-60 seconds) added to the comparison. Large gaps between what you see and what the library reports usually mean the clocks are badly out of sync or the server applies its own leeway.

What is JWT leeway and how much should I set?

Leeway is a grace period, in seconds, that a validator adds when comparing nbf and exp to absorb clock drift between servers. On a single machine, 0 seconds is correct. Across containers, regions or third-party identity providers, 30-60 seconds is common. Never set leeway larger than the token's remaining lifetime — a nearly-expired token could be accepted for far longer than intended.

What does TokenExpiredError / ExpiredSignatureError mean in practice?

It means the library verified the signature successfully AND the current time passed exp (possibly after leeway). That is the strongest 'expired' signal you can get: the token is authentic and it is dead. In jsonwebtoken the error carries expiredAt so you can log the exact instant; PyJWT&rsquo;s ExpiredSignatureError is raised before claims are returned, so fetch the payload separately if you need it for logging.

Can I check if a JWT is expired without the signing secret?

Yes. Expiration lives in the payload, which is only Base64URL-encoded — anyone can decode it. Compare exp with the current time and you know the answer. What you cannot do without the secret (or public key) is verify the signature, so a token that 'looks valid' from decoding may still be a forged one. For a security answer, use a verifying library with the right key.

How do I check if a JWT is expired in Java?

Use JJWT (io.jsonwebtoken): Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token) throws io.jsonwebtoken.ExpiredJwtException when the token is past exp. The exception carries the claims, so e.getClaims().getExpiration() gives you the exact expiration instant. You can also read exp manually and compare against Instant.now().getEpochSecond().

How do I check if a JWT is expired in C# / .NET?

Use the System.IdentityModel.Tokens.Jwt package. Build a TokenValidationParameters with ValidateLifetime = true, then call a JwtSecurityTokenHandler().ValidateToken(...). An expired token throws SecurityTokenExpiredException, whose Expires property is the exact expiration DateTime. Beware the default ClockSkew of 5 minutes: tokens are accepted for up to 5 minutes past exp unless you set ClockSkew = TimeSpan.Zero.

How do I convert the JWT exp epoch timestamp to a date?

exp is Unix seconds, so multiply by 1000 and pass to new Date(...) in JavaScript (or new Date(seconds * 1000)), use datetime.fromtimestamp(exp, tz=timezone.utc) in Python, Instant.ofEpochSecond(exp) in Java, or DateTimeOffset.FromUnixTimeSeconds(exp) in .NET. The claim is an absolute instant, so the displayed date shifts with your local timezone.

Check a token’s expiry in 3 seconds — 100% in your browser

Paste a JWT and see its exp, iat and nbf claims as human-readable timestamps with a live valid / expiring soon / expired verdict. Nothing leaves your machine.

Related guides