Skip to content

JWT Access Tokens — Short-Lived Tokens for Stateless API Authorization

DodaTech Updated 2026-06-28 4 min read

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

JWT access tokens are short-lived tokens containing user identity and authorization claims, sent with every API request to prove the client is authenticated and authorized.

What You'll Learn

How access tokens work, what claims to include, appropriate expiry times, and how to validate access tokens on every request.

Why It Matters

Access tokens are the primary credential for API requests. Getting their structure, expiry, and validation right is critical for both security and user experience. Too short = poor UX. Too long = security risk.

Real-World Use

GitHub API tokens expire after configurable periods. Google API tokens last 3600 seconds. Durga Antivirus Pro uses 15-minute access tokens for its dashboard API — short enough to limit breach damage, long enough for smooth UX.

flowchart LR
    A["Client"] -->|"Request + Access Token"| B["API Server"]
    B -->|"Validate signature"| C["Valid?"]
    C -->|"Yes"| D{"Check expiry"}
    D -->|"Not expired"| E["Check scopes"]
    E -->|"Sufficient"| F["200 OK"]
    C -->|"No"| G["401 Unauthorized"]
    D -->|"Expired"| G
    E -->|"Insufficient"| H["403 Forbidden"]
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style F fill:#dcfce7,stroke:#16a34a
    style G fill:#fecaca,stroke:#dc2626
    style H fill:#fecaca,stroke:#dc2626

Access Token Claims

{
  "iss": "https://auth.dodatech.com",
  "sub": "user-123456",
  "aud": "https://api.dodatech.com",
  "exp": 1718000000,
  "iat": 1717996400,
  "scope": "threat:read threat:write",
  "role": "analyst"
}

Code Example: Access Token Validation Middleware

from flask import Flask, request, jsonify, g
import jwt, datetime, os

app = Flask(__name__)
SECRET = os.environ.get("JWT_SECRET")
ISSUER = "https://auth.dodatech.com"
AUDIENCE = "https://api.dodatech.com"

def require_access_token(f):
    def wrapper(*args, **kwargs):
        auth = request.headers.get("Authorization", "")
        if not auth.startswith("Bearer "):
            return jsonify({"error": "Missing access token"}), 401

        token = auth[7:]
        try:
            payload = jwt.decode(
                token,
                SECRET,
                algorithms=["HS256"],
                options={
                    "require": ["exp", "iss", "aud"],
                    "verify_exp": True
                },
                issuer=ISSUER,
                audience=AUDIENCE
            )

            # Check token type
            if payload.get("token_type") != "access":
                return jsonify({"error": "Invalid token type"}), 401

            g.user = payload
        except jwt.ExpiredSignatureError:
            return jsonify({"error": "Access token expired"}), 401
        except jwt.InvalidAudienceError:
            return jsonify({"error": "Invalid audience"}), 401
        except jwt.InvalidIssuerError:
            return jsonify({"error": "Invalid issuer"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"error": "Invalid access token"}), 401

        return f(*args, **kwargs)
    return wrapper

@app.route("/api/threats")
@require_access_token
def get_threats():
    return jsonify({
        "user": g.user["sub"],
        "role": g.user.get("role"),
        "scopes": g.user.get("scope")
    })

Access Token Lifetime Guidelines

Context Recommended TTL Reasoning
Standard web app 15 minutes Good balance of security and UX
Mobile app 1 hour Less frequent refresh (battery)
High-security API 5 minutes Limit breach damage
Internal microservice 1 hour Trusted network, less churn
Third-party integration 30 minutes Standard OAuth2 practice

Common Mistakes

1. Making Access Tokens Too Long

24-hour access tokens defeat the purpose of short-lived tokens. If stolen, the attacker has a day of access. Use 15 minutes.

2. Not Including Token Type

Without token_type: access in the payload, a refresh token could be used as an access token. Always include the token type.

3. Not Validating aud and iss

A token issued for service A should not work for service B. Validate audience and issuer on every request.

4. Sending Access Tokens in URLs

Passing ?token=xxx in the URL exposes the token in logs and browser history. Always use the Authorization header.

5. Not Handling Expired Tokens Gracefully

The client should silently refresh the token and retry. Returning a raw 401 with no instructions creates a poor UX.

Practice Questions

  1. How long should an access token typically live?
  2. What claims should every access token include?
  3. How does the client send the access token to the server?
  4. What happens when an access token expires?
  5. Why should access tokens include a token_type claim?

Answers:

  1. 15 minutes for most applications. Adjust based on security requirements and refresh frequency.
  2. iss (issuer), sub (subject/user), aud (audience), exp (expiry), iat (issued at), and scope (permissions).
  3. Via the Authorization: Bearer <token> HTTP header. Never in URL query parameters.
  4. The server returns 401. The client should use a refresh token to obtain a new access token and retry.
  5. To prevent token confusion — a refresh token or ID token should not be accepted as an access token.

Challenge: Build a Flask middleware that validates access tokens, checks expiry, verifies audience and issuer, and returns specific error messages for each failure case.

FAQ

What is the difference between an access token and a refresh token?

An access token is short-lived (minutes) and authorizes API requests. A refresh token is long-lived (days) and obtains new access tokens.

Can an access token be revoked?

Stateless JWT access tokens cannot be revoked individually. Use short expiry or maintain a blocklist (Redis) of revoked token IDs.

Should access tokens contain user roles?

Yes. Include roles and permissions in the access token claims. The resource server uses these for authorization decisions.

How do I prevent access token replay?

Use HTTPS, short expiry, and include jti (unique token ID). For high-security, add token binding (mTLS).

What if a client needs to make many requests?

Each request carries the same access token until it expires. The server validates on every request. Use short expiry with silent refresh.

Mini Project

Build a Flask API with JWT access token authentication. Include a login endpoint that issues access tokens, a middleware that validates them, and protected endpoints that use token claims for authorization.

What's Next

Now learn about JWT Refresh Tokens — the counterpart to access tokens for maintaining sessions.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro