What Is JWT? Complete Guide to JSON Web Tokens
A deep dive into how JSON Web Token (JWT) expiration works, common pitfalls, and security considerations for modern authentication.
Table of Contents
Quick Summary
exp claim located in the token payload. This claim contains a Unix timestamp representing the exact second the token becomes invalid. Because JWTs are stateless, managing proper expiration windows and refresh token architectures is critical to application security.What is a JWT?
A JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
JWTs are most commonly used for authorization. Once a user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
The "exp" Claim Explained
Within the payload (the middle section of the JWT), there are standard fields called "claims." The exp (Expiration Time) claim is one of the registered claims defined by the specification.
The exp claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the exp claim requires that the current date/time MUST be before the expiration date/time listed in the token.
Unix Timestamps in JWTs
A very common source of bugs in JWT implementations relates to the unit of time used. The JWT specification explicitly requires the exp claim to be a NumericDate.
A NumericDate is defined as the number of seconds from the Unix Epoch (1970-01-01T00:00:00Z UTC) until the specified date/time. This ignores leap seconds.
Seconds vs Milliseconds
Date.now() returns milliseconds. If you pass this directly into a JWT payload as the exp claim, the token will technically be valid for another 50,000+ years! Always divide by 1000 and round down when generating an exp claim in JS.Why Expiration is Critical
Unlike traditional session cookies, which the server can destroy at any time by deleting the session from its database, JWTs are stateless.
When a backend service receives a JWT, it does not query a database to check if the user is still active; it simply verifies the cryptographic signature. Therefore, if a hacker steals a JWT, they can impersonate the user until the token expires. This is why giving a JWT a lifespan of "1 year" is a massive security vulnerability.
Refresh Token Architectures
To solve the stateless vulnerability, modern architectures use a combination of two tokens:
- Access Token (JWT): Very short-lived (e.g., 15 minutes). Used for accessing resources.
- Refresh Token: Long-lived (e.g., 7 days) and stored securely (often as an HttpOnly cookie). Used exclusively to request new Access Tokens.
When the 15-minute Access Token expires, the client sends the Refresh Token to a specific endpoint. The server validates the Refresh Token against its database (checking if the user was banned or logged out), and if valid, issues a new short-lived Access Token.
Common Mistakes
Trusting Client-Side Validation
exp claim to see if the user's session has expired. However, you must never rely on the frontend for security. A malicious user can intercept the request and modify the frontend code. The backend must always strictly validate the exp claim during the signature verification process.- Ignoring Clock Skew: Different servers across a network might have slightly different system clocks. It's a best practice to allow a small leeway (e.g., 30-60 seconds) when validating the
expclaim to prevent random failures. - Using Milliseconds: As mentioned above, accidentally setting the expiry in milliseconds creates practically infinite tokens.
Security Best Practices
Implement these practices to harden your JWT architecture:
- Keep Access Tokens short-lived (under 1 hour).
- Never store sensitive data (like passwords or PII) inside the JWT payload. Anyone can read it.
- Use strong, randomly generated secrets for HMAC signing (at least 256 bits).
- Rotate your signing keys periodically.
Decoding JWTs in Code
Because the payload is simply Base64Url encoded, you can decode a JWT and read the exp claim natively without needing a heavy cryptographic library.
JavaScript (Client-Side)
// Decode the middle part of the JWT
function getJwtExpiry(token) {
const payload = token.split('.')[1];
// Convert Base64Url to standard Base64
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
const { exp } = JSON.parse(jsonPayload);
// Convert seconds to milliseconds for JS Date
return new Date(exp * 1000);
}Generating Valid Exp (Node.js)
// Creating an exp claim 15 minutes from now
const nowInSeconds = Math.floor(Date.now() / 1000);
const exp = nowInSeconds + (15 * 60);
const payload = {
userId: "123",
exp: exp
};Frequently Asked Questions
Why is the JWT exp claim in seconds instead of milliseconds?
The JWT specification (RFC 7519) mandates that the exp (Expiration Time) claim must be a NumericDate value, which is explicitly defined as the number of seconds since the Unix Epoch. This was done to match existing Unix standards and keep the token payload as compact as possible.
Can I revoke a JWT before it expires?
JWTs are stateless, meaning they cannot be inherently revoked by the issuer once signed. To revoke a token, you must implement a database blacklist or rely on short expiration times combined with a refresh token architecture.
Try It Yourself
Need to quickly check when a token expires without writing code? Use our completely secure, client-side JWT decoder.