Skip to content
Owais Khan Software Reviews
How-To Guides

Resolving JWT Expired at Current Time: Inspection, Validation, and Fixes

Learn why a JWT is expired at the current time, how to calculate exp versus Unix epoch timestamps, and how to fix clock skew and token refresh flows.

By Owais Khan

A JSON Web Token (JWT) is expired at the current time when the token’s exp (expiration time) claim holds a numeric timestamp that is less than or equal to the current system time of the verifying server. Under RFC 7519, this timestamp is expressed as a NumericDate in seconds since January 1, 1970, 00:00:00 UTC, meaning any validation check rejects the token the instant the server’s Unix clock reaches or passes that stored number.

Encountering an expiration failure at the exact moment of a request typically indicates one of three root issues: the token’s active lifetime has naturally elapsed, the verifying environment suffers from server clock skew, or the evaluating system incorrectly misinterprets UTC timestamps against a localized machine time. Addressing this failure requires inspecting the raw token payload, verifying system clocks against coordinated universal time, providing clock skew tolerance during verification, or invoking a refresh token exchange before dispatching network calls.

Raw JWT Payload Before Correcting Expiration Failures

When an application rejects an incoming credential with a 401 status or an expired token exception, the raw token carries an unverified payload that must be inspected against the current epoch timestamp. Below is a raw, base64url-encoded JWT alongside its decoded payload JSON and the raw state of the environment that causes the validation error.

Raw encoded token string:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3ItODk0MiIsImlhdCI6MTczODU4ODgwMCwibmJmIjoxNzM4NTg4ODAwLCJleHAiOjE3Mzg1ODk3MDAsInJvbGUiOiJkZXZlbG9wZXIifQ.sample_signature_digest_placeholder

Decoded raw JSON payload:

{
 "sub": "usr-8942",
 "iat": 1738588800,
 "nbf": 1738588800,
 "exp": 1738589700,
 "role": "developer"
}

Environment state at time of verification failure:

  • Verifying server system time: 1738589735 (expressed in seconds since Unix epoch).
  • Target comparison rule: Current time must be strictly before exp.
  • Evaluation logic: exp <= now checks whether 1738589700 <= 1738589735, which evaluates to true (token expired).
  • Result: Immediate authentication rejection and HTTP 401 Token Expired error thrown on request arrival.
  • Client behavior: The client attempted to send the expired token without inspecting expiration or exchanging a refresh credential.

Validated Verification Pipeline and Refresh Flow After Applying Fixes

Applying systematic remediation resolves the failure across both the server and client tiers. On the server side, clock tolerance absorbs system clock skew, while the client implementation preemptively checks expiration and uses a refresh token to obtain a fresh access token before the API endpoint rejects the transmission.

Updated client-side inspection and refresh routine:

// Decode the token payload safely to check validity
function parseJwtPayload(token) {
 const segments = token.split('.');
 if (segments.length!== 3) {
 throw new Error('Invalid JWT format');
 }
 return JSON.parse(Buffer.from(segments[1], 'base64url').toString('utf8'));
}

// Determine whether current time has reached or passed exp
function checkIsExpired(token) {
 const payload = parseJwtPayload(token);
 const currentEpochSeconds = Math.floor(Date.now() / 1000);
 return payload.exp <= currentEpochSeconds;
}

// Handle request dispatch using refresh tokens
async function dispatchAuthorizedRequest(accessToken, refreshToken) {
 let activeToken = accessToken;

 // Check expiry before sending to avoid server-side 401 errors
 if (checkIsExpired(activeToken)) {
 // Use the refresh token to acquire a newly minted access token
 const response = await fetch('/auth/refresh', {
 method: 'POST',
 headers: { 'Content-Type': 'application/json' },
 body: JSON.stringify({ refreshToken })
 });
 const data = await response.json();
 activeToken = data.accessToken;
 }

 return fetch('/api/resource', {
 headers: { Authorization: `Bearer ${activeToken}` }
 });
}

Updated server-side verification configuration:

const jwt = require('jsonwebtoken');

function authenticateIncomingToken(token, secretKey) {
 try {
 // Configure clockTolerance to accommodate small clock skews across microservices
 const decoded = jwt.verify(token, secretKey, {
 clockTolerance: 60 // Allow a 60-second window
 });
 return { valid: true, payload: decoded };
 } catch (error) {
 return { valid: false, error: error.message };
 }
}

Python server verification with skew allowance:

import jwt

def verify_bearer_token(token, secret_key):
 try:
 # leeway parameter adds clock tolerance against minor system clock differences
 payload = jwt.decode(
 token,
 secret_key,
 algorithms=["HS256"],
 leeway=60
 )
 return {"valid": True, "payload": payload}
 except jwt.ExpiredSignatureError:
 return {"valid": False, "error": "Token expired"}
 except jwt.InvalidTokenError as err:
 return {"valid": False, "error": str(err)}

Detailed Technical Breakdown of Every Expiration Factor

Transforming an expired authentication state into a resilient architecture requires understanding how time claims interact with machine clocks and network lifecycles.

The Anatomy of NumericDate in Claims

Under RFC 7519, the time-related claims inside a standard payload—specifically iat (issued at), nbf (not before), and exp (expiration time)—are formatted as a NumericDate. This format represents the count of seconds (not milliseconds) from 1970-01-01T00:00:00Z UTC, deliberately excluding leap seconds. This conforms to the POSIX.1 IEEE Std 1003.1 definition where each standard calendar day contains exactly 86400 seconds. Because this timestamp directly tracks elapsed seconds from the universal UTC epoch, time zones technically do not alter the underlying numeric value. An issuing service at UTC+00 and an evaluating service in UTC-05 or UTC+09 produce the identical epoch integer for the identical instant in time.

Mathematical Evaluation of Expiry

During token verification, an evaluating engine evaluates the claim condition: the current time must be strictly before the timestamp specified in exp. Stated in comparison logic, if current_time >= exp (or exp <= current_time), the token MUST NOT be accepted by the receiving server. In our baseline example, the token parameters were:

  • iat: 1738588800 (equivalent to Mon, 03 Feb 2025 13:20:00 UTC)
  • exp: 1738589700 (equivalent to Mon, 03 Feb 2025 13:35:00 UTC)
  • Total designed token lifetime: 1738589700 - 1738588800 = 900 seconds (exactly 15 minutes)

When the verifying server evaluated the request at timestamp 1738589735 (13:35:35 UTC), now exceeded exp by 35 seconds. Consequently, the token was rejected immediately as expired.

Clock Skew and Drift Across Environments

Distributed microservices often display time drift or misconfigured clocks. If an authentication server generates a token with a 15-minute lifetime, but the verifying resource server’s system clock runs 40 seconds ahead of universal coordinated time, that resource server will reject valid incoming requests 40 seconds before their intended expiration window closes.

Similarly, if an instance or container sets its localized regional clock improperly rather than referencing true UTC, internal comparison libraries might evaluate the token’s epoch timestamp against an offset local value, triggering unexpected expiry exceptions. Adding a clock tolerance (such as setting clockTolerance: 60 in Node.js or leeway=60 in Python) allows an evaluating engine to accept tokens that fall within a 60-second discrepancy boundary, absorbing minor drift without compromising security.

Refresh Token Pairing Architecture

JWT expiration operates as a passive revocation system. Because a signed token cannot be un-issued once granted, short lifetimes limit the window of exposure if a credential is leaked or intercepted. Rather than extending access token durations to hours or days—which elevates security risks—modern architectures employ two distinct token lifecycles:

  1. Short-lived access tokens: Typically configured for brief durations, such as 15 minutes, used directly in API request headers.
  2. Long-lived refresh tokens: Typically configured for multiple days (such as 7 days), securely stored and presented only to the authentication server’s refresh endpoint to generate a new access token when the active credential expires.

Rather than forcing the API server to return an HTTP 401 error, proactive client applications inspect the payload locally and initiate token refreshing before dispatching network calls.

Verifying and Adjusting Expiration on Your Own Tokens

You can diagnose and resolve expiration errors on any JWT in your stack by following a step-by-step audit.

Step 1: Decode and Inspect the Payload

A JWT consists of three segments separated by periods: header, payload, and signature. To view the expiration date without running cryptographic signature verification, take the middle segment (index 1), convert from base64url encoding to UTF-8 text, and parse the JSON string. If you want to check your payload quickly without writing local scripts, you can inspect the values using the JWT Decoder.

Verify that the three standard registered time claims are present and correct:

  • iat: Identifies when the token was created and allows calculating its overall age.
  • nbf: Specifies that the token must not be accepted prior to this time.
  • exp: Specifies that the token must not be accepted at or after this time.

If the exp claim is absent entirely or contains an invalid non-numeric format, your authentication library may reject the token or flag a format error.

Step 2: Compare Against Current UTC Epoch Seconds

Determine whether the token is genuinely expired by comparing exp with the current Unix timestamp:

In JavaScript or Node.js:

const nowInSeconds = Math.floor(Date.now() / 1000);
const isExpired = payload.exp <= nowInSeconds;
const secondsRemaining = payload.exp - nowInSeconds;

In Python:

import time

now_in_seconds = int(time.time())
is_expired = payload.get("exp", 0) <= now_in_seconds
seconds_remaining = payload.get("exp", 0) - now_in_seconds

If secondsRemaining is negative, the token is expired, and the absolute value reveals how long ago expiration occurred.

Step 3: Check Machine System Clocks for Skew

Run the following command on both the authentication server and verifying resource servers to verify their clocks are synchronized to UTC:

date -u

If the output shows a discrepancy between your host machines, configure Network Time Protocol (NTP) synchronization to keep system clocks aligned. If server environments cannot be modified immediately, configure your verification libraries to accept a clock skew tolerance of 30 to 60 seconds via clockTolerance or leeway parameters.

Step 4: Implement Preemptive Client Refreshing

To prevent user interruption, configure your client application to evaluate payload.exp <= Math.floor(Date.now() / 1000) prior to sending an HTTP request. If the token is expired, call the authorization server’s refresh endpoint using the refresh token, replace the expired access token with the newly issued access token, and then proceed with the target API call.

Open the JWT Decoder

Frequently asked questions

What does it mean when a JWT is expired at the current time?
It means the verifying server's current timestamp (in seconds since the Unix epoch) is greater than or equal to the numeric value stored in the token's exp claim. Under RFC 7519, servers must reject tokens once the current time reaches or passes that expiration point.
How is the expiration claim formatted in a JWT?
The exp claim is stored as a NumericDate, which is a JSON numeric value representing seconds since the Unix epoch (1970-01-01T00:00:00Z UTC), ignoring leap seconds.
Can server timezone differences cause unexpected token expiration?
Because NumericDate is based on seconds since the Unix epoch in UTC, local timezones should not affect the value. However, if a server's underlying system clock is set to a localized time instead of true UTC, verification libraries will compare against the wrong reference time and reject valid tokens.
How can clock skew between microservices be handled during JWT verification?
You can configure a clock tolerance or leeway option in your verification library (such as clockTolerance in Node.js or leeway in Python), which allows a small buffer, such as 60 seconds, to accommodate minor differences between system clocks.
What is the best way for a client to handle an expired access token?
Rather than waiting for an API to return a 401 error, the client should inspect the exp claim before dispatching the request. If expired, it should use a refresh token to request a new access token from the authentication server and send the fresh token with the request.