Skip to content

Token Authentication — Stateless Auth with Dynamic Access Tokens

DodaTech Updated 2026-06-28 5 min read

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

Token authentication is a stateless method where the server issues a signed token after login, and the client includes this token in subsequent requests to prove identity.

What You'll Learn

How token authentication works, stateless vs stateful tokens, implementation patterns, and when to choose tokens over API keys or session cookies.

Why It Matters

Unlike API keys (static) or session cookies (stateful on the server), tokens are dynamic and self-contained. The server does not need to store session data — the token itself contains all the information needed to verify identity. This enables horizontal scaling without shared session storage.

Real-World Use

GitHub API uses personal access tokens, Firebase uses ID tokens, and most modern APIs use Bearer tokens (JWT or opaque) for authentication.

flowchart LR
    A["Client"] -->|"POST /login\n(username, password)"| B["Auth Server"]
    B -->|"Issue token"| A
    A -->|"GET /data\nAuthorization: Bearer "| C["API Server"]
    C -->|"Verify signature"| D["Token Valid?"]
    D -->|"Yes"| E["200 OK + Data"]
    D -->|"No"| F["401 Unauthorized"]
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style C fill:#fef3c7,stroke:#d97706
    style E fill:#dcfce7,stroke:#16a34a
    style F fill:#fecaca,stroke:#dc2626

Token Types

Type Description Example
Opaque Token Random string, server must look up state Session ID, random UUID
JWT Signed token with embedded claims eyJhbGci... (header.payload.signature)
Reference Token Opaque key that references server-stored data OAuth2 introspection token

Code Example: Token Generation and Verification

import secrets
import hashlib
import time
from flask import Flask, request, jsonify

app = Flask(__name__)

# In production, use Redis with TTL
active_tokens = {}
USERS = {"admin": "password123"}

@app.route("/api/login", methods=["POST"])
def login():
    data = request.get_json()
    password = USERS.get(data.get("username"))
    if not password or password != data.get("password"):
        return jsonify({"error": "Invalid credentials"}), 401

    token = secrets.token_hex(32)
    token_hash = hashlib.sha256(token.encode()).hexdigest()
    active_tokens[token_hash] = {
        "user": data["username"],
        "expires": time.time() + 3600  # 1 hour
    }
    return jsonify({"token": token, "expires_in": 3600})

def validate_token():
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer "):
        return None
    token = auth[7:]
    token_hash = hashlib.sha256(token.encode()).hexdigest()
    token_data = active_tokens.get(token_hash)
    if not token_data:
        return None
    if time.time() > token_data["expires"]:
        del active_tokens[token_hash]
        return None
    return token_data

@app.route("/api/profile")
def profile():
    session = validate_token()
    if not session:
        return jsonify({"error": "Invalid or expired token"}), 401
    return jsonify({"user": session["user"], "message": "Authenticated"})

if __name__ == "__main__":
    app.run()

Expected output:

$ curl -X POST -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password123"}' \
  http://localhost:5000/api/login
{"token":"a1b2c3d4...","expires_in":3600}

$ curl -H "Authorization: Bearer a1b2c3d4..." \
  http://localhost:5000/api/profile
{"user":"admin","message":"Authenticated"}

Code Example: Token Refresh

@app.route("/api/refresh", methods=["POST"])
def refresh_token():
    session = validate_token()
    if not session:
        return jsonify({"error": "Invalid token"}), 401

    # Generate new token, invalidate old one
    new_token = secrets.token_hex(32)
    new_hash = hashlib.sha256(new_token.encode()).hexdigest()
    old_hash = hashlib.sha256(
        request.headers["Authorization"][7:].encode()
    ).hexdigest()

    active_tokens[new_hash] = active_tokens.pop(old_hash)
    active_tokens[new_hash]["expires"] = time.time() + 3600

    return jsonify({"token": new_token, "expires_in": 3600})

Common Mistakes

1. Storing Tokens in localStorage Without Protection

XSS Attacks can read localStorage. For web apps, use httpOnly cookies or store tokens in memory. For mobile apps, use secure device storage (Keychain/Keystore).

2. Token Not Expiring

Tokens without expiry are valid forever once stolen. Always set short expiry times (15 minutes to 1 hour) and provide refresh tokens.

3. Not Validating Token on Every Request

Some developers cache token validation. Validate every request — token revocation can happen at any time.

4. Including Sensitive Data in Tokens

Opaque tokens stored server-side are safer. If using JWT, never include passwords or secrets in the payload.

5. Confusing Token Types

Using an opaque token reference where a self-contained JWT is needed (or vice versa) adds unnecessary complexity. Choose based on your architecture.

Practice Questions

  1. What is the difference between stateless and stateful tokens?
  2. Why should tokens have an expiration time?
  3. How does the client send a token to the server?
  4. What is a refresh token and why is it needed?
  5. What happens when a token expires mid-request?

Answers:

  1. Stateless tokens (JWT) contain all data to verify; stateful tokens (opaque) require server-side lookup. Stateless scales better but cannot be revoked easily.
  2. Short expiry limits the window of opportunity if a token is stolen. The attacker can only use it until expiration.
  3. Via the Authorization: Bearer <token> header. Avoid query parameters or body.
  4. A refresh token is a long-lived credential that obtains new short-lived access tokens, avoiding frequent re-login.
  5. The server returns 401. The client should catch this, use the refresh token to get a new access token, and retry the request.

Challenge: Implement a complete token-based auth system with login, token validation middleware, token refresh, and logout (token invalidation). Use Redis or an in-memory store.

FAQ

How is token auth different from session cookies?

Token auth is stateless — the server does not store session data. Session cookies reference server-stored state. Tokens scale better horizontally.

Can I revoke a token?

For stateful (opaque) tokens, yes — delete from the server store. For stateless (JWT) tokens, use a blocklist or short expiry with token rotation.

Should I use JWT or opaque tokens?

Use JWT when you need stateless auth and the token claims are non-sensitive. Use opaque tokens when you need server-side revocation control.

How long should tokens live?

Access tokens: 15 minutes to 1 hour. Refresh tokens: 7 to 30 days. Shorter for sensitive applications, longer for convenience.

Is token auth the same as OAuth2?

No. Token auth is a pattern; OAuth2 is a framework that uses tokens. Token auth can exist without OAuth2, and OAuth2 always uses tokens.

Mini Project

Build a Flask API with token-based authentication: login endpoint, token validation middleware, protected profile endpoint, token refresh, and logout. Test the full flow with curl.

What's Next

Now explore JWT Token Authentication where tokens become self-contained with embedded user claims and cryptographic signatures.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro