Skip to content

JWT Token Expiry — Managing Token Lifetimes and Expiration Strategies

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about JWT Token Expiry. We cover key concepts, practical examples, and best practices to help you master this topic.

JWT token expiry is managed through the exp claim which sets an absolute expiration timestamp, after which the token is rejected by the server.

What You'll Learn

How the exp claim works, choosing appropriate TTL values, handling clock skew, and implementing silent token refresh for seamless user experience.

Why It Matters

Token expiry is the primary defense against stolen tokens. Short expiry limits damage but creates more refresh requests. Long expiry improves UX but increases risk. Finding the right balance is critical.

Real-World Use

Google APIs use 3600-second access tokens. GitHub allows configurable token expiry (30-90 days for personal access tokens). Banking APIs use 5-minute tokens for high-security operations.

flowchart LR
    A["Token Issued\niat + exp"] --> B["Active Period"]
    B --> C{"Time > exp?"}
    C -->|"No"| D["Process Request"]
    C -->|"Yes"| E["401 Expired"]
    E --> F["Client Refreshes"]
    F --> G["New Token Issued"]
    G --> B
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#dcfce7,stroke:#16a34a
    style E fill:#fecaca,stroke:#dc2626
    style G fill:#dbeafe,stroke:#2563eb

The exp Claim

The exp claim contains a Unix timestamp (seconds since epoch) indicating when the token expires.

{
  "exp": 1718000000,
  "iat": 1717996400
}

The token above was issued at timestamp 1717996400 and expires at 1718000000 — a 1-hour lifetime.

Choosing Token TTL

Application Access Token TTL Reasoning
Banking API 5 minutes Maximum security
Standard Web App 15 minutes Good balance
Mobile App 1 hour Less frequent refresh
Internal Service 1 hour Trusted network
IoT Device 24 hours Limited connectivity

Code Example: Handling Token Expiry

import jwt
import datetime
import time

SECRET = "your-secret"

# Issuing a token with 1-hour expiry
def issue_token(user_id):
    payload = {
        "sub": user_id,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }
    return jwt.encode(payload, SECRET, algorithm="HS256")

# Client-side: check expiry before making request
def is_token_expired(token):
    try:
        # Decode without verification to check expiry
        payload = jwt.decode(
            token,
            options={"verify_signature": False}
        )
        return time.time() > payload["exp"]
    except Exception:
        return True

# Client-side: refresh if expired
def make_api_call(token, refresh_token):
    if is_token_expired(token):
        # Refresh the token
        response = requests.post(
            "https://auth.example.com/refresh",
            json={"refresh_token": refresh_token}
        )
        if response.status_code == 200:
            token = response.json()["access_token"]
        else:
            raise Exception("Session expired. Re-login required.")

    # Make the actual API call
    return requests.get(
        "https://api.example.com/data",
        headers={"Authorization": f"Bearer {token}"}
    )

Handling Clock Skew

Servers and clients may have slightly different clocks. JWT libraries typically allow a small leeway (default 0 seconds).

# Allow 30 seconds of clock skew
payload = jwt.decode(
    token,
    SECRET,
    algorithms=["HS256"],
    leeway=30  # seconds of tolerance
)

Grace Period Strategy

Some systems implement a grace period where an expired token is accepted once to allow a seamless refresh without the client seeing a 401:

# Server-side: allow expired tokens for refresh endpoint only
@app.route("/api/auth/refresh", methods=["POST"])
def refresh():
    data = request.get_json()
    old_access = data.get("access_token")

    try:
        # Allow expired tokens (but not invalid ones)
        payload = jwt.decode(
            old_access,
            SECRET,
            algorithms=["HS256"],
            options={"verify_exp": False}  # Accept expired
        )
        # Additional checks...
    except jwt.InvalidTokenError:
        return jsonify({"error": "Invalid token"}), 401

Common Mistakes

1. Not Setting exp Claim

Tokens without expiry never expire. If stolen, the attacker has indefinite access. Always set exp.

2. Making Tokens Permanent

24-hour access tokens with 30-day refresh tokens are reasonable. Annual tokens are dangerous.

3. Forgetting Clock Skew

If servers are 2 minutes apart, a token valid on the issuer is expired on the verifier. Use leeway.

4. Not Checking Expiry Client-Side

Making a request with a definitely expired token wastes a round-trip. Check expiry client-side before calling.

5. Using String Dates Instead of Timestamps

The exp claim must be a numeric Unix timestamp. String dates are not standard and may not be validated.

Practice Questions

  1. What claim is used for JWT expiration?
  2. How does the server handle an expired token?
  3. What is clock skew and how do you handle it?
  4. Why should access tokens be shorter-lived than refresh tokens?
  5. What is a grace period for token refresh?

Answers:

  1. The exp (expiration) claim, which contains a Unix timestamp.
  2. The server returns 401 Unauthorized. Most JWT libraries throw ExpiredSignatureError.
  3. Clock skew is the time difference between servers. Handle it with the leeway parameter in JWT decode.
  4. Access tokens are sent with every request — short expiry limits exposure. Refresh tokens are sent rarely.
  5. A grace period allows an expired access token to be used once for the refresh endpoint, enabling seamless renewal.

Challenge: Implement a token refresh system with a 30-second grace period where expired tokens can still refresh, but the new token gets a reduced TTL as a safety measure.

FAQ

What happens if the exp claim is missing?

JWT libraries may or may not reject tokens without exp. Always validate exp explicitly. Some libraries require 'verify_exp': True.

Can I extend a token's expiry?

No. To extend, issue a new token with a later exp. The client should use the refresh token to get a new access token.

What is a good access token TTL?

15 minutes for most applications. Adjust based on your security requirements and refresh token capabilities.

How do I handle timezones in JWT expiry?

Always use UTC Unix timestamps for exp, iat, and nbf. Never use local time or timezone-aware strings.

What if the token expires mid-upload?

For long operations (file uploads), use a separate upload token with longer expiry or chunked uploads with progress tracking.

Mini Project

Build a token management system that issues tokens with configurable TTL, validates expiry on every request, handles clock skew with leeway, and provides a refresh endpoint with grace period.

What's Next

Now learn about JWT Token Storage — where to store tokens securely on web and mobile clients.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro