Security

What is JWT expiration?

Understand JWT expiration, how the exp claim works, and why token expiration is critical for security and implementing proper refresh mechanisms.

By Inventive HQ Team

JWT expiration is a built-in deadline that tells any server how long a token may be trusted. It lives in the token's exp (expiration time) claim, whose value is a NumericDate — the number of seconds since the Unix epoch (1970-01-01T00:00:00Z UTC), as defined by RFC 7519. When a server verifies a JWT, it rejects the token if the current time is at or past exp. Because the deadline is signed into the token itself, expiration works as an automatic, passive revocation: a stolen token stops working on its own, without the server keeping any list of invalid tokens. Most systems pair a short-lived access token (minutes) with a longer-lived refresh token (days) so users stay logged in while the credential actually sent on every request expires quickly.

That is the summary an AI overview would give you. The part it can't give you is the mental model of a token's lifetime — the three timestamps that bound it, why you can't un-issue a JWT once it's out, and how the numbers actually get computed. Here is the whole picture.

A JWT's lifetime: iat, nbf, exp on one timeline

Every JWT carries up to three time claims. iat marks when it was born, nbf marks the earliest moment it may be used, and exp marks the moment it dies. The window between nbf and exp is the only stretch during which a receiver will accept the token. The animation below follows the "current time" marker across that lifetime.

The lifetime of a JWT A horizontal timeline showing issued-at (iat), not-before (nbf), and expiration (exp). A marker for the current time travels left to right; the token is only valid in the shaded window between nbf and exp. The lifetime of a JWT All three claims are NumericDate: seconds since the Unix epoch token accepted here iat issued at nbf not before exp expires rejected rejected now

The JWT spec (RFC 7519, Section 4.1) defines exactly three registered time claims. All share one format — NumericDate, seconds (not milliseconds) since the Unix epoch — and all are optional, though exp is used almost universally.

ClaimNameFormatWhat it means
expExpiration TimeNumericDate (seconds since Unix epoch)The token MUST NOT be accepted at or after this time. This is the expiry.
iatIssued AtNumericDate (seconds since Unix epoch)When the token was created. Used to measure the token's age (and, with exp, its total lifetime).
nbfNot BeforeNumericDate (seconds since Unix epoch)The token MUST NOT be accepted before this time. Useful for tokens that should activate later.

A key subtlety: exp is checked as "current time is before exp" while nbf is checked as "current time is after or equal to nbf." Both comparisons run against the verifying server's clock, which is why clock skew and NTP synchronization (covered below) matter.

The Concept of JWT Expiration

JWT expiration is a security feature that limits how long a token can be used. The "exp" (expiration time) claim specifies when a token becomes invalid and should no longer be accepted by the server. Once a token expires, presenting it is no longer sufficient for authentication—the user must obtain a new token.

Expiration is critical because it limits the damage if a token is stolen or compromised. An attacker who captures a token gains access only until it expires. Without expiration, a compromised token could be used indefinitely, potentially forever. Expiration provides a natural revocation mechanism even without maintaining a database of invalid tokens.

Most authentication systems use short-lived access tokens (minutes to hours) to minimize the window of vulnerability, combined with longer-lived refresh tokens that can obtain new access tokens. This architecture balances security and user experience—users don't need to log in constantly, but compromised tokens have limited utility.

Want to inspect a real token's exp, iat, and nbf right now? Paste one into the decoder below — it runs entirely in your browser and never sends the token anywhere.

Loading interactive tool...

How JWT Expiration Works

The "exp" claim contains a numeric value representing seconds since the Unix epoch (January 1, 1970). For example, "exp":1735689600 represents January 1, 2025, at 00:00:00 UTC. When the server verifies a JWT, it checks whether the current time is before the expiration time. If the current time is past the expiration time, the token is invalid.

Verification frameworks handle expiration automatically. Most JWT libraries check the exp claim by default and reject expired tokens. In Node.js with jsonwebtoken:

const decoded = jwt.verify(token, secret);
// If token is expired, verify throws an error

The verification automatically rejects tokens where current_time > exp. You don't need to manually check expiration—the library does it.

Calculating expiration times is straightforward. To create a token that expires in one hour:

const payload = {
  sub: 'user_123',
  exp: Math.floor(Date.now() / 1000) + (60 * 60)  // 1 hour from now
};

const token = jwt.sign(payload, secret);

The calculation adds 3600 seconds (one hour) to the current Unix timestamp. This approach works across time zones since Unix timestamps are always UTC.

Advertisement

Choosing Appropriate Expiration Times

Selecting expiration times involves balancing security and user experience. Short expiration times improve security—a compromised token can be used for a shorter period. Long expiration times improve user experience—users don't need to re-authenticate as frequently.

For access tokens used in API requests, typical expiration ranges are 5 minutes to 1 hour. Many modern APIs use 15 minutes, providing a reasonable balance. Very short expiration (5 minutes) offers maximum security but may impact user experience with frequent interruptions.

For browser-based applications, shorter expiration times are generally safe since the entire token is transmitted with each request, making it easier to refresh automatically. A 15-minute access token is reasonable for web applications.

For refresh tokens, much longer expiration (days to weeks) is appropriate since they're used less frequently and stored securely. Refresh tokens might expire in 7 days or even 30 days, but they should still expire eventually. Indefinite refresh token validity creates security risks.

For service-to-service authentication, expiration might be shorter (5 minutes) since services can request new tokens automatically. For background jobs, expiration can be longer since the token is issued and used within a predictable timeframe.

The Refresh Token Pattern

The refresh token pattern elegantly solves the expiration dilemma. Instead of requiring long-lived access tokens, you issue short-lived access tokens and separate, longer-lived refresh tokens:

  1. User logs in and receives both access token (15 minutes) and refresh token (7 days)
  2. Client uses the access token for API requests
  3. When the access token expires, client sends the refresh token to get a new access token
  4. The refresh token can obtain multiple new access tokens before it expires
  5. When the refresh token expires, the user must log in again

This pattern allows users to remain logged in for days while access tokens expire after minutes. If an access token is compromised, it's useful only for 15 minutes. If a refresh token is compromised, it can be used for 7 days but only to obtain new access tokens.

Implementation in Node.js:

// Login endpoint
app.post('/login', (req, res) => {
  const accessToken = jwt.sign(
    { sub: user.id },
    secret,
    { expiresIn: '15m' }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, type: 'refresh' },
    refreshSecret,
    { expiresIn: '7d' }
  );

  res.json({ accessToken, refreshToken });
});

// Refresh endpoint
app.post('/refresh', (req, res) => {
  const refreshToken = req.body.refreshToken;

  try {
    const decoded = jwt.verify(refreshToken, refreshSecret);

    const newAccessToken = jwt.sign(
      { sub: decoded.sub },
      secret,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

The refresh token and access token use different secrets, allowing them to be verified independently. The refresh token includes a type claim to distinguish it from access tokens.

Clock Skew and Time Synchronization

JWT expiration verification compares the exp claim to the current server time. If servers have significant time differences, legitimate tokens might appear expired on some servers. Distributed systems must maintain reasonable time synchronization.

Most organizations use NTP (Network Time Protocol) to synchronize time across servers. This keeps time differences within milliseconds. JWT libraries provide a clock skew tolerance to handle minor discrepancies:

jwt.verify(token, secret, { clockTolerance: 10 });  // 10 seconds tolerance

This allows tokens to remain valid for 10 seconds past their expiration time, accommodating minor time differences. Set clock tolerance to a reasonable value (5-30 seconds), not minutes.

Revocation Alongside Expiration

Expiration is a passive revocation mechanism—tokens automatically become invalid at a certain time. However, sometimes you need active revocation before expiration, such as when a user logs out or when suspicious activity is detected.

Several approaches complement expiration-based revocation. The simplest is token blacklisting: maintain a database of revoked token IDs and check it during verification. However, this adds a database lookup to every request, impacting performance.

Alternative approaches include:

  1. JWT ID (jti) and revocation: Include a unique token ID and check it against a revocation list
  2. Short expiration + rotation: Keep expiration short and rotate tokens frequently
  3. Session identifier: Include a session ID and invalidate sessions when needed
  4. Version tracking: Include a version number and increment it to invalidate all old tokens

For simple systems, relying on short expiration without explicit revocation is acceptable. For systems requiring user logout or session termination, implement one of these approaches.

Handling Expired Tokens in Frontend Applications

Frontend applications should detect when tokens expire and handle the situation gracefully. When an API returns a 401 Unauthorized response, the client should:

  1. Check if the access token is expired
  2. If expired, attempt to refresh using the refresh token
  3. If refresh succeeds, retry the original request with the new token
  4. If refresh fails (refresh token expired), redirect to login

This flow allows users to remain logged in as long as their refresh token is valid, transparently refreshing expired access tokens. Implement this in an HTTP interceptor:

// Example using axios
api.interceptors.response.use(
  response => response,
  error => {
    const originalRequest = error.config;

    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      return refreshAccessToken()
        .then(newToken => {
          originalRequest.headers.Authorization = `Bearer ${newToken}`;
          return api(originalRequest);
        })
        .catch(() => {
          // Refresh failed, redirect to login
          window.location.href = '/login';
        });
    }

    return Promise.reject(error);
  }
);

Best Practices for JWT Expiration

Always set expiration times on tokens. Never create indefinitely valid tokens. At minimum, set some reasonable expiration, preferably relatively short.

Use the refresh token pattern for applications with human users. Short-lived access tokens paired with longer-lived refresh tokens provide optimal security and user experience.

Keep access token expiration short—typically 5 minutes to 1 hour depending on your risk tolerance. Shorter expiration times improve security significantly.

Keep refresh token expiration long enough for user convenience but not indefinitely. Consider 7 days to 30 days typical. Log out functionality should invalidate refresh tokens.

Synchronize server time using NTP. Don't rely on client-provided timestamps. The server determines what the current time is for expiration validation.

Don't implement custom clock skew handling beyond what your JWT library provides. Small clock tolerances are reasonable, but large tolerances compromise security.

Implement active revocation mechanisms for scenarios requiring logout before token expiration. Blacklisting or session invalidation handles these cases.

Conclusion

JWT expiration limits the lifetime of tokens, automatically restricting access if a token is compromised. Combined with the refresh token pattern, expiration enables both security and user convenience. Short-lived access tokens expire within minutes, limiting damage from compromise, while longer-lived refresh tokens maintain user sessions. Always set appropriate expiration times, implement the refresh token pattern for applications with users, and maintain server time synchronization. Expiration, combined with signature verification, creates a secure token-based authentication system.

Frequently Asked Questions

What is JWT expiration?

JWT expiration is a built-in time limit on how long a token is accepted. It is carried in the "exp" (expiration time) claim inside the token's payload, whose value is a NumericDate — the number of seconds since the Unix epoch (1970-01-01T00:00:00Z UTC), as defined by RFC 7519. When a server verifies a token, it rejects it if the current time is at or past the exp time. Because the deadline is baked into the signed token, expiration acts as an automatic, passive revocation: a stolen token stops working on its own once exp passes, with no database lookup required.

Is the JWT exp claim in seconds or milliseconds?

Seconds. The exp, iat, and nbf claims are all NumericDate values, meaning seconds since the Unix epoch — not milliseconds. This trips up JavaScript developers constantly, because JavaScript's Date.now() returns milliseconds. You must divide by 1000: Math.floor(Date.now() / 1000). If you accidentally store milliseconds in exp, the token will appear to expire roughly 50,000 years in the future and effectively never dies.

What happens when a JWT expires?

When a token's exp time passes, the next server that verifies it rejects the request — typically with a 401 Unauthorized response. Most JWT libraries check exp automatically and throw an error (for example, jsonwebtoken throws TokenExpiredError). The token is not deleted or changed; it simply stops being accepted. The client must then obtain a new token, either by using a refresh token or by having the user log in again.

What is a good JWT expiration time?

For access tokens, 5 minutes to 1 hour is typical, with 15 minutes a common default that balances security and user experience. Refresh tokens live much longer — usually 7 to 30 days — because they are used less often and stored more carefully. There is no universal number: shorter expiration limits the damage from a stolen token, while longer expiration reduces how often users and services must re-authenticate. High-risk systems trend toward the short end.

Can you revoke a JWT before it expires?

Not with a plain, stateless JWT. Because the token is self-contained and validated only by checking its signature and exp, the server has no built-in way to cancel it early — it stays valid until exp no matter what. To revoke before expiry you must add infrastructure: a denylist/blocklist of revoked token IDs (jti) checked on every request, a server-side session lookup, or a token-version counter. Most teams avoid the per-request lookup by keeping access tokens very short-lived, so a revoked token dies within minutes anyway.

What is the difference between iat, nbf, and exp?

All three are NumericDate timestamps (seconds since the Unix epoch). "iat" (issued at) records when the token was created and can be used to measure its age. "nbf" (not before) is the earliest time the token may be accepted — the token is invalid before it. "exp" (expiration time) is the latest time the token may be accepted — the token is invalid at or after it. Together, nbf and exp define the window during which the token is valid, and iat marks its birth.

Does the server or the client check JWT expiration?

The receiver (the server or API validating the token) enforces expiration, and it must use its own clock — never a client-supplied timestamp. The client cannot be trusted to honestly report the time or to stop using an expired token. This is why server time synchronization (NTP) matters: expiration is only as reliable as the verifying server's clock.

What is clock skew in JWT validation?

Clock skew is the small difference between the clocks of the server that issued a token and the server that verifies it. If the verifier's clock runs slightly ahead, it may reject a token that is technically still valid. To handle this, JWT libraries offer a clock-tolerance (leeway) setting — commonly 5 to 30 seconds — that allows a token to be accepted for a few seconds past its exp. Keep this leeway small; large tolerances effectively extend every token's lifetime and weaken security.

What is the difference between access token and refresh token expiration?

An access token is sent with every API request, so it is kept short-lived (minutes) to limit exposure if it leaks. A refresh token is presented only to an auth endpoint to mint new access tokens, so it can live much longer (days to weeks) while spending most of its life stored securely and unused. If an access token is stolen it is useful for only minutes; if a refresh token is stolen it is more dangerous, which is why refresh tokens are stored carefully and can be rotated and revoked.

jwtexpirationtokenssecurityauthentication