13.6. Tokens
Table of Contents
Why Tokens Matter in Authentication
In modern backend systems, tokens are one of the main ways to keep users logged in and to protect APIs. Instead of sending a username and password with every request, the client sends a token. The backend checks the token to decide who the user is and whether to allow the request.
Tokens are especially important for:
- Single Page Applications (SPAs) in the browser.
- Mobile apps.
- APIs that multiple services or clients need to access.
- Stateless authentication, where the server does not keep session state in memory.
A token is not a password, but it acts like one for a limited time. Anyone who has a valid token can usually act as that user until it expires.
Because of this, token handling and storage must be done carefully.
Token Basics
What Is a Token?
A token is a structured piece of data, usually a string, that represents:
- Who the user or client is.
- What they are allowed to do.
- How long the token is valid.
You can think of it like a movie ticket:
- The ticket represents that you paid.
- It has your seat, time, and cinema.
- The cinema checks it at the entrance.
- After the movie time passes, the ticket is no longer valid.
Similarly, a token:
- Is issued by an authentication server.
- Is sent by the client with each request.
- Is verified by the backend before allowing access.
- Has an expiry time after which it is rejected.
Tokens are usually:
- Opaque: a random string, only the server knows what it means.
- Structured: like JSON Web Tokens (JWT) that contain readable data and are signed.
You will study JWTs in detail in the dedicated JSON Web Tokens chapter. Here we focus on the general idea of tokens.
Tokens vs Sessions
Both tokens and sessions try to solve the same problem: "How do we keep a user authenticated across multiple requests without sending the password every time?"
Traditional Session Based Authentication
With sessions:
- User logs in with username and password.
- Server checks credentials.
- Server creates a session record in a database or memory, for example:
| session_id | user_id | created_at | expires_at |
|-------------------|--------:|----------------------|----------------------|
| abc123xyz | 7 | 2026-08-27 10:00:00 | 2026-08-27 12:00:00 |
- Server sends a session ID to the client as a cookie.
- On each request, the browser sends the cookie.
- The server looks up the session ID in storage and finds the user.
So with sessions, the server stores the session, and the client only sends a session identifier.
Token Based Authentication
With tokens:
- User logs in with username and password.
- Server checks credentials.
- Server creates a token that already contains user identity and other claims, or that can be used to look up that information.
- Server sends the token to the client.
- On each request, the client sends the token, often in the
Authorizationheader:
Authorization: Bearer <token-value>- The server validates the token and extracts the user information.
In many token systems, the server does not need to store anything about the token. The token itself has all the information and is verified using cryptography. JWTs are a common format for that.
Comparison
| Feature | Sessions | Tokens |
|---|---|---|
| Storage location | Server side (DB, memory, cache) | Client side (browser, mobile app, etc.) |
| What client stores | Session ID | Entire token |
| Scaling across servers | Needs shared session store | Easier, can be stateless |
| Revoking access early | Easy, delete session in store | Harder if token is self contained |
| Typical transport method | Cookies | Headers, sometimes cookies |
| Ideal for | Traditional web apps | APIs, SPAs, mobile apps |
Sessions rely on server side state, tokens can be stateless. Stateless tokens are easier to scale horizontally, but harder to revoke before expiration.
Types of Tokens
Different kinds of tokens exist for different use cases. You will meet some of them in later chapters.
Access Tokens
An access token allows a client to access protected resources for a limited time.
Typical properties:
- Short lived, for example 5 minutes to 1 hour.
- Sent with each request to the API.
- Contains information about the user and their permissions, or a reference to them.
- If stolen, can be used until it expires.
Example usage in an HTTP request:
GET /api/user/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...You will study access tokens in detail in the "Access Tokens" chapter.
Refresh Tokens
A refresh token is used to obtain new access tokens without forcing the user to log in again.
Typical properties:
- Longer lived, for example days or weeks.
- Sent only to a specific endpoint for refreshing, for example
/auth/refresh. - Should be stored more securely than access tokens.
- Often stored in a database so they can be revoked.
You will study them in the "Refresh Tokens" chapter.
ID Tokens
In some systems, especially with OpenID Connect, an ID token represents the user's identity and basic profile information. It is mostly used by the client and not by APIs.
Typical properties:
- Contains user identity claims: user id, email, name.
- Often a JWT.
- Not used to call APIs directly, that is the role of the access token.
Opaque Tokens vs Structured Tokens
- Opaque token
A random string with no visible structure. Example:l5AMGqBzC2kK5FmXlOy7xZ1J0sPq9R.
Only the server that issued it knows what it means. - Structured token
A token that contains data in a defined format, for example a JWT with JSON payload that can be decoded.
Example differences:
| Aspect | Opaque Token | Structured Token (for example JWT) |
|---|---|---|
| Human readable | No | Partially (header and payload) |
| Size | Usually shorter | Usually longer (header + payload + sig) |
| Server storage | Usually requires lookup in store | May not require server storage |
| Debugging | Harder | Easier to inspect during development |
Token Lifecycle
A token has a life cycle from creation to expiration or revocation.
1. Issuance
Tokens are created after a successful authentication step.
Example flow:
- User sends credentials to
/auth/login. - Server verifies credentials.
- Server creates an access token, and maybe a refresh token.
- Server returns the tokens in the response.
Example response:
{
"access_token": "<access-token-here>",
"refresh_token": "<refresh-token-here>",
"token_type": "bearer",
"expires_in": 3600
}The client then stores these tokens somewhere.
2. Storage on the Client
How tokens are stored depends on the client type:
- Mobile app
Stored in secure storage provided by the OS. - Browser SPA
Common options: - In memory (for example JavaScript variable).
- In
localStorageorsessionStorage. - In cookies.
Each option has security trade-offs that you will consider in security related chapters.
3. Usage
For every protected API call:
- Client reads the access token from its storage.
- Client sends it in the request, usually in the
Authorizationheader:
Authorization: Bearer <access-token>- Server validates the token.
- If valid, server processes the request.
4. Expiration
Tokens should not live forever. Each token has:
- An exp or similar field with expiration time, or
- A server side record with expiration information.
After expiration:
- Server rejects requests with that token.
- Client should handle errors like
401 Unauthorizedand maybe try to refresh.
Always set an expiration time for tokens. Non expiring tokens are a serious security risk.
5. Renewal
When an access token expires:
- Client uses a refresh token and calls a special endpoint, for example
/auth/refresh. - If the refresh token is valid and not revoked, server issues a new access token.
- Optionally server issues a new refresh token and invalidates the old one.
This pattern is explained in more detail in the "Refresh Tokens" chapter.
6. Revocation
Sometimes you must make a token invalid before its natural expiration time. For example:
- User clicks "Logout".
- User reports their account is compromised.
- You rotate secrets used to sign tokens.
How to revoke depends on your design:
- For opaque tokens stored in a database, delete or mark them as revoked.
- For structured stateless tokens, maintain a blacklist or a token version number in the user record, and reject tokens that no longer match.
Example approach with a version:
| user_id | token_version |
|---|---|
| 7 | 3 |
Token payload contains token_version: 3. When you want to invalidate all existing tokens, increment token_version to 4 in the database. Any token with version 3 is rejected from that point.
Token Transport
Where Tokens Are Sent
Common transport methods:
- Authorization header (recommended for APIs):
Authorization: Bearer <token>- Clear where and how the token is used.
- Works well for SPAs, mobile apps, and server to server communication.
- Cookies:
- Token is stored as a cookie and sent automatically by the browser.
- Can combine token based authentication with cookie properties like
HttpOnlyandSecure. - Query parameters (generally not recommended):
- Example:
GET /api/data?access_token=<token> - Tokens can be logged in HTTP logs and proxies.
- Only use when there is no other option, and be very careful.
Prefer the Authorization: Bearer <token> header for API access. Avoid sending tokens in URLs.
Example Full Request with Token
GET /api/orders HTTP/1.1
Host: api.shop.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/jsonServer side pseudo code:
def get_orders(request):
auth_header = request.headers.get("Authorization")
user = authenticate_with_token(auth_header)
if not user:
return Response(status=401, body={"detail": "Invalid token"})
orders = load_orders_for_user(user.id)
return Response(status=200, body=orders)Token Security Considerations
Tokens are extremely powerful, so you must treat them carefully.
Token Confidentiality
If an attacker gets a valid token, the attacker can usually act as that user until the token expires.
Basic rules:
- Use HTTPS for all API calls.
- Never log full tokens in application logs.
- Avoid storing tokens in insecure browser storage if possible, especially when vulnerable to XSS.
Example of a risky log line:
# Bad: do not log full tokens
logger.info("Auth failed for token %s", token)Better:
# Better: log a short hash or a few characters
logger.info("Auth failed for token starting with %s", token[:6])Token Expiration and Short Lifetimes
Short lived tokens help limit damage if a token is stolen.
Example:
- Access token lifetime: 15 minutes.
- Refresh token lifetime: 14 days.
If an access token is stolen:
- Attacker can use it for at most 15 minutes.
- If you detect the breach, you can revoke refresh tokens.
Token Scope and Permissions
Tokens should not give more permissions than necessary.
Examples:
- A token only for reading user profile, not for modifying payment methods.
- A token only for a specific service, not for everything.
This is sometimes called scopes or permissions:
read:profilewrite:ordersadmin:users
The server must check scopes when authorizing each request.
Use the principle of least privilege. Give each token only the minimum permissions it needs.
Token Replay Attacks
A replay attack happens when an attacker captures a valid token and uses it later.
To reduce this risk:
- Use HTTPS, do not send tokens over plain HTTP.
- Keep token lifetimes short.
- Use refresh tokens with careful storage.
- In sensitive cases, use one time tokens or bind tokens to a client or device where possible.
Stateless vs Stateful Token Systems
Stateless Tokens
With stateless tokens, like self-contained JWTs:
- Server does not store token state by default.
- All information is inside the token.
- Validation uses a secret key or public key, not a database lookup.
Advantages:
- Easy to scale horizontally, no shared session storage.
- APIs can be served by many instances without coordination.
Disadvantages:
- Harder to revoke tokens early.
- If a token is created with faulty permissions, it is valid until expiry.
- Token size can grow if you add many claims.
Stateful Tokens
With stateful tokens:
- Server stores token records in a database or cache.
- Each incoming token requires a lookup.
Advantages:
- Easy to revoke tokens by deleting or updating a record.
- Easy to track token usage, devices, etc.
Disadvantages:
- Requires a shared database or cache.
- Adds load to your storage system.
In real systems, you often mix ideas:
- Access tokens may be stateless.
- Refresh tokens may be stateful and stored in a database.
Example: Simple Token Based Login Flow
Imagine a very simple backend that uses opaque access tokens stored in memory. This is only for understanding, not for production.
Step 1: Login and Token Issuance
Pseudo code:
import uuid
from datetime import datetime, timedelta
# In memory token store: token -> (user_id, expires_at)
TOKENS = {}
def login(username: str, password: str):
user = find_user_by_username(username)
if not user or not verify_password(password, user.hashed_password):
raise AuthenticationError("Invalid credentials")
token = uuid.uuid4().hex
expires_at = datetime.utcnow() + timedelta(hours=1)
TOKENS[token] = (user.id, expires_at)
return {
"access_token": token,
"token_type": "bearer",
"expires_in": 3600,
}Step 2: Using the Token to Call a Protected Endpoint
Client sends:
GET /api/me HTTP/1.1
Host: api.example.com
Authorization: Bearer 7d40c9f4c4d84520b3ed4cb1a657df2eServer checks:
def authenticate_with_token(authorization_header: str):
if not authorization_header:
return None
prefix, _, token = authorization_header.partition(" ")
if prefix.lower() != "bearer" or not token:
return None
data = TOKENS.get(token)
if not data:
return None
user_id, expires_at = data
if datetime.utcnow() > expires_at:
# Token expired, remove it
del TOKENS[token]
return None
return find_user_by_id(user_id)Endpoint:
def get_me(request):
user = authenticate_with_token(request.headers.get("Authorization"))
if not user:
return Response(status=401, body={"detail": "Not authenticated"})
return Response(status=200, body={"id": user.id, "username": user.username})This example shows the core idea of tokens:
- Authenticate once.
- Receive a token.
- Send the token with each request.
- Server validates the token instead of asking for username and password again.
In real production systems you will:
- Use secure storage instead of an in memory dictionary.
- Use cryptographically signed tokens.
- Use HTTPS everywhere.
- Add refresh tokens, scopes, and more detailed validation.
Summary
In this chapter you learned:
- A token is a piece of data that represents an authenticated user or client.
- Tokens are an alternative to traditional server side sessions and help with stateless, scalable APIs.
- Different token types exist, including access tokens, refresh tokens, and ID tokens.
- Tokens have a lifecycle: issuance, storage, usage, expiration, renewal, and revocation.
- Tokens are usually sent in the
Authorization: Bearer <token>header. - Token security depends on confidentiality, expiration, limited scope, and protection against replay.
- Stateless tokens are easier to scale, while stateful tokens are easier to revoke.
Next chapters will build on these foundations and focus on specific token types like JSON Web Tokens, access tokens, refresh tokens, and protocols like OAuth 2.0 and OpenID Connect.
Views: 5
KAHIBARO