Every time a user logs into a SaaS application, an
auth token
is generated and handed to their browser or mobile app. That token carries a silent clock inside it. The
JWT expiration
field, stored as a plain Unix timestamp, tells every server that reads it exactly when to stop trusting the credential. Get that timestamp wrong by even a few seconds and users either get locked out too early or, worse, carry a valid session far longer than your security policy allows. Understanding how
JSON web token
claims work, especially
exp
and
iat
, is one of the most practical things a SaaS team can do to tighten authentication without adding friction.
Key Takeaways:
-
The
expandiatclaims in a JWT JSON web token are always Unix timestamps measured in whole seconds, not milliseconds. - Clock skew between servers can silently invalidate tokens or extend sessions beyond policy - a 60-second tolerance window is the industry standard fix.
- Setting token expiry too long increases breach risk; setting it too short breaks user experience. A concrete formula helps you pick the right value.
- You can decode and verify any JSON web token timestamp in seconds using a Unix epoch converter, no library required.
Content Table
What Is a JWT and Why Do Timestamps Live Inside It
A JSON web token is a compact, URL-safe credential made of three Base64-encoded parts separated by dots: a header, a payload, and a signature. The payload is where time-sensitive data lives. Because JWTs are stateless, the server does not look up a session in a database to validate them. Instead, it reads the claims baked into the payload and trusts the cryptographic signature. That design is fast and scalable, but it creates a real constraint: once a token is issued, the server cannot "delete" it. The only reliable way to limit its lifetime is to embed an expiration timestamp directly in the token itself.
This is why time, specifically Unix epoch time, is central to JWT security. The RFC 7519 specification that defines JWTs mandates that time-related claims be expressed as "NumericDate" values, which are simply the number of seconds since January 1, 1970 UTC. No timezones. No locale formatting. Just an integer that any server anywhere in the world can compare against its own clock.
The
exp
and
iat
Claims Explained
The JWT specification defines several registered claims. Two matter most for token lifecycle management:
-
exp(Expiration Time): The timestamp after which the token must not be accepted. Servers are required to reject any token where the current time is equal to or greater than this value. This is the core mechanism behind JWT expiration. -
iat(Issued At): The timestamp at which the token was created. The iat claim is not enforced by default, but it is enormously useful. It lets you calculate how old a token is, enforce a maximum age policy independently ofexp, and detect tokens that were issued suspiciously far in the past.
A third claim,
nbf
(Not Before), defines the earliest time a token is valid. It is less commonly used but follows the same Unix timestamp format.
All three values are integers. If your system generates them in milliseconds (a common mistake when using JavaScript's
Date.now()
), the resulting number will be roughly 1,000 times larger than expected. A server checking
exp
against the current epoch second will see a token that appears to expire in the year 33,658 and will never reject it. This is a real production bug that has affected multiple SaaS teams.
A Concrete Example: Decoding Real Token Timestamps
Suppose your authentication service issues the following JWT payload after a user logs in at 10:00 AM UTC on July 1, 2025:
{
"sub": "user_8821",
"iat": 1751364000,
"exp": 1751367600,
"role": "admin"
}
Let's break down what those numbers mean:
| Claim | Unix Timestamp | Human-Readable (UTC) | Meaning |
|---|---|---|---|
iat
|
1751364000 | 2025-07-01 10:00:00 | Token was issued at this moment |
exp
|
1751367600 | 2025-07-01 11:00:00 | Token expires exactly 1 hour later |
The difference is exactly 3,600 seconds, which is one hour. Any server that receives this token after 11:00:00 UTC must reject it. To verify this manually, you subtract
iat
from
exp
: 1751367600 - 1751364000 = 3600 seconds. You can confirm the human-readable equivalent of any of these values instantly using a Unix timestamp converter, which is exactly the kind of quick sanity check that prevents the milliseconds-vs-seconds bug mentioned earlier.
For deeper context on how databases should store these values, see our guide on
Unix timestamps in databases, since the same integer format applies whether you are storing
exp
in a token or in an audit log table.
Clock Skew: The Silent Token Killer
Here is a real constraint that catches teams off guard. In a distributed system, your authentication server and your API server are two different machines. Their system clocks are synchronized via NTP, but they are never perfectly aligned. A difference of 30 to 90 seconds is common in cloud environments.
Consider this scenario: a token with
exp = 1751367600
is validated by an API server whose clock reads 1751367610, just 10 seconds past the expiry. The token is rejected. The user gets a 401 error. From their perspective, they were just logged in and the app broke.
The standard fix is to build a clock skew tolerance into your validation logic. Most JWT libraries support a leeway parameter. A value of 60 seconds is widely accepted and is referenced in the OpenID Connect Core specification. Set it, document it, and make sure every service in your stack uses the same value.
Practical rule:
Add a 60-second leeway to
exp
validation. Never add leeway to
iat
checks, because that would allow accepting tokens issued in the future, which is a red flag for replay attacks.
Choosing the Right Token Expiry for Your SaaS Product
The right token expiry value depends on the sensitivity of your application and the expected session behavior. Here is a practical framework:
- High-sensitivity apps (banking, healthcare, admin dashboards): 15 to 30 minutes for access tokens. Use refresh tokens to silently renew sessions.
- Standard SaaS apps (project management, CRM, analytics): 1 to 8 hours. Match the typical working session length.
- Low-risk or read-only APIs: Up to 24 hours, but pair with token rotation on sensitive actions.
- Machine-to-machine service tokens: Can be longer (days or weeks), but should be scoped narrowly and rotated on a schedule.
A useful formula for calculating your
exp
value at issuance time:
// Node.js example
const iat = Math.floor(Date.now() / 1000); // current time in seconds
const ttl = 3600; // time-to-live: 1 hour in seconds
const exp = iat + ttl;
const payload = {
sub: userId,
iat: iat,
exp: exp
};
Notice the explicit division by 1000 to convert from milliseconds to seconds. This single line prevents the most common JWT timestamp bug in JavaScript applications.
Actionable Steps to Implement Secure JWT Expiration
Here is a concrete checklist you can apply to any SaaS authentication system today:
-
Always include both
iatandexp. Theiatclaim is technically optional, but it gives you an audit trail and enables max-age enforcement independent of expiry. -
Verify the timestamp format before deploying.
Decode your token and check that
expis a 10-digit integer. A 13-digit value means milliseconds crept in. - Set a clock skew leeway of 60 seconds in your validation middleware. Apply it consistently across every service that consumes auth tokens.
- Use short-lived access tokens with refresh tokens. A 15-minute access token paired with a 7-day refresh token is far more secure than a single 7-day access token, because the refresh token can be revoked server-side.
-
Log the
iatvalue on every authenticated request. This lets you detect anomalies, such as the same token being used from two different geographic regions simultaneously. -
Test expiry in staging with accelerated time.
Mock your clock to jump past the
expvalue and confirm your app handles the 401 gracefully rather than crashing or looping.
If you need to quickly convert a raw
exp
value from a token into a readable date, or verify what timestamp to set for a given duration, the
epoch converter on our homepage
handles both directions instantly, no code required.
Conclusion
JWT expiration is not a complex concept, but it is one where small mistakes create large security gaps. The
exp
and
iat
claims are just integers, Unix timestamps counted in seconds from the epoch. That simplicity is their strength: every server, every language, and every platform can compare two numbers without ambiguity. The real skill is in applying them correctly, choosing a sensible
token expiry
duration, handling clock skew with a tolerance window, and catching the milliseconds-vs-seconds mistake before it ships. Build these habits into your auth pipeline and your
JSON web token
implementation will be both secure and maintainable.
Verify JWT Timestamps Instantly - No Code Needed
Paste any Unix timestamp from a JWT payload and convert it to a human-readable date in one click. Catch milliseconds-vs-seconds errors before they reach production.
Try Our Free Tool →
exp
(Expiration Time) is the Unix timestamp after which the token is invalid and must be rejected.
iat
(Issued At) is the timestamp when the token was created. Both are integers in seconds. The
iat
claim is optional but useful for auditing and enforcing maximum token age policies.
Unix timestamps are timezone-independent integers, making them unambiguous across all servers and regions. Formatted date strings like "2025-07-01T10:00:00" can be misinterpreted based on locale or timezone settings. An integer comparison is also faster and less error-prone than string parsing in high-throughput authentication systems.
A long-lived token stays valid even if the user's account is compromised or their permissions change. Since JWTs are stateless, the server cannot revoke them mid-lifetime. If an attacker steals a token with a 30-day expiry, they have 30 days of access. Short expiry windows combined with refresh tokens significantly reduce this risk window.
In JavaScript, always use
Math.floor(Date.now() / 1000)
when generating
iat
or
exp
values. The raw
Date.now()
returns milliseconds. Dividing by 1000 converts it to the seconds-based integer the JWT specification requires. Verify by checking that your timestamp is a 10-digit number, not 13 digits.
Clock skew is the small time difference between two servers' system clocks in a distributed system. Even a 30-second difference can cause a valid token to be rejected if the validating server's clock is slightly ahead. The standard fix is to configure a 60-second leeway in your JWT validation library to absorb normal NTP drift.