STATUS: ONLINE UPTIME: 00:00:00 THEME: LIGHT

$ route /posts/how-api-tokens-work-and-how-to-manage-them --ready

SYSTEM: POST.EXE
$ cat /posts/how-api-tokens-work-and-how-to-manage-them.md
$ render how-api-tokens-work-and-how-to-manage-them
How API Tokens Work and How to Manage Them in Modern Systems thumbnail
date: May 25, 2026 | read: ~22 min

Authentication looks trivial in tutorials. Generate a token, send it with requests, validate it on the backend. Ship it. But then production happens. Tokens get leaked. Services can’t agree on identity. Microservices forward tokens through twelve hops. A stolen refresh token grants access for weeks. Suddenly, what seemed like a solved problem becomes one of the hardest parts of building distributed systems. This article explains how API tokens actually work, why they became the standard, and the operational nightmares that come with managing identity at scale.

What API Tokens Actually Are

An API token is a credential that proves identity without revealing the secret that established that identity. Think of it like a hotel room key card. You proved your identity at check-in with your ID and credit card, and in return you got a card that grants access to your room. The card doesn’t contain your passport or credit card number—it’s a temporary credential with limited scope. If you lose it, the hotel deactivates it without changing your actual identity.

Contrast this with passwords. A password is the secret itself. Every time you use it, you’re transmitting the master key. If it’s intercepted, the attacker has everything. If you use the same password across services, one breach compromises all of them. Passwords require secure storage with expensive hashing algorithms. They’re reusable, which makes them valuable targets.

Tokens solve this by separating authentication (proving who you are) from authorization (proving you can access a resource). You authenticate once with your password. The system issues a token with an expiration time and specific permissions. You use that token for subsequent requests. If the token is stolen, it’s time-limited and scoped. You can revoke it without changing your password. Multiple tokens can coexist for different devices or applications.

Session-based authentication sits between passwords and tokens. After login, the server creates a session ID and stores session state server-side (usually in memory or a database). The client receives the session ID as a cookie. On each request, the server looks up the session to validate it. This works, but it’s stateful—the server must maintain session storage, sync it across instances, and handle session expiration. Tokens, especially JWTs, enable stateless authentication where the token itself contains all necessary information.

Modern systems prefer tokens because they scale horizontally. No shared session state. No database lookup on every request. The token carries its own validation data. This becomes critical in microservices architectures where dozens of services need to validate identity independently without coordinating through a central session store.

How Token Authentication Works in Practice

The lifecycle of a token reveals why distributed systems adopted this pattern. Here’s what happens when a mobile app authenticates with a backend:

Step 1: Authentication request. The client sends credentials (username/password, OAuth code, certificate, or biometric proof) to an authentication service. This is the only time the actual secret is transmitted. It happens over HTTPS, and the credentials should never be logged.

Step 2: Validation. The authentication service verifies credentials against its user database. This might involve checking a password hash, validating an OAuth authorization code with an identity provider, or verifying a cryptographic signature. If validation fails, the request is rejected. No token is issued.

Step 3: Token generation. The authentication service generates a token containing identity information (user ID, roles, permissions), metadata (expiration time, issuer, audience), and signs it with a secret key or private key. The signature prevents tampering. The token is returned to the client.

Step 4: Storage. The client stores the token securely. For web browsers, this might be an HTTP-only cookie or local storage (with trade-offs we’ll discuss). For mobile apps, it’s the system keychain. For server-to-server communication, it’s environment variables or secret management systems.

Step 5: Token transmission. For every API request, the client includes the token in the Authorization header: Authorization: Bearer <token>. The term “Bearer” means “whoever possesses this token has authority”—which is why secure storage matters.

Step 6: Validation by backend services. Each service that receives the request validates the token. For JWTs, this means verifying the signature using the public key, checking expiration, and ensuring the audience and issuer match expected values. No database lookup required. For opaque tokens, this means calling a token introspection endpoint or looking up token metadata in a cache.

Step 7: Authorization decision. The service extracts claims from the token (user ID, roles, scopes) and makes an access control decision. Does this user have permission to read this resource? To delete it? The token provides identity, but business logic determines authorization.

Token authentication flow in microservices:

architecture-beta
  group client(logos:programming-browser)[Client Layer]
  group auth(logos:security-shield-network)[Authentication]
  group services(logos:server-api-cloud)[Microservices]
  group storage(logos:database)[Storage]

  service app(logos:worldwide-web-browser)[Mobile App] in client
  service authsvc(logos:security-it-service)[Auth Service] in auth
  service api1(logos:terminal)[Product API] in services
  service api2(logos:terminal)[Payment API] in services
  service api3(logos:terminal)[User API] in services
  service db(logos:database)[User DB] in storage
  service secrets(logos:security-shield-wall)[Secret Store] in storage

  app:R --> L:authsvc
  authsvc:B --> T:db
  authsvc:B --> T:secrets
  app:R --> L:api1
  app:R --> L:api2
  app:R --> L:api3
  api1:B --> T:secrets
  api2:B --> T:secrets
  api3:B --> T:secrets

This looks simple. The complexity comes from edge cases. What happens when the token expires mid-request? When a service is compromised and its validation keys are stolen? When you need to revoke a token immediately but it’s stateless? These are the problems that make token management hard.

Token Types and When to Use Each

Not all tokens are created equal. Each type solves different problems and comes with different trade-offs.

Session tokens are opaque identifiers (usually UUIDs) that reference server-side session state. The token itself contains no information. The server looks it up in a session store (Redis, Memcached, database) to find associated user data. Advantage: immediate revocation—delete the session and the token is invalid. Disadvantage: requires shared state across all services and a database lookup on every request. Best for monolithic applications or when immediate revocation is critical.

JWTs (JSON Web Tokens) are self-contained tokens that carry identity and claims in a signed JSON payload. The signature proves authenticity. No server-side storage required. Advantage: stateless validation—any service can verify the token independently using the public key. Disadvantage: cannot be revoked before expiration without maintaining a blocklist (which reintroduces state). Best for microservices, API gateways, and distributed systems where stateless validation matters.

OAuth access tokens are credentials issued by an authorization server that grant access to protected resources. They can be JWTs or opaque tokens. They’re typically short-lived (minutes to hours) and scoped to specific permissions. Advantage: follows standardized OAuth protocol with well-defined flows. Disadvantage: requires OAuth infrastructure and token introspection for opaque tokens. Best for third-party integrations and public APIs.

Refresh tokens are long-lived credentials (days to months) used to obtain new access tokens without re-authenticating. They should be stored securely and rotated on each use. Advantage: enables short-lived access tokens without constant re-authentication. Disadvantage: if stolen, grants long-term access until detected. Best for mobile and web applications where re-authentication is disruptive.

API keys are simple string tokens (often just random characters) used for server-to-server authentication. They identify the calling application, not a specific user. Advantage: simple to implement and rotate. Disadvantage: no standard format, often long-lived, and frequently committed to code repositories by mistake. Best for internal service-to-service communication and public API rate limiting.

Comparison of token types:

Token Type Lifespan Revocation State Required Use Case
Session Token Hours Immediate Yes (session store) Monolithic apps
JWT Minutes–Hours Delayed No (stateless) Microservices
OAuth Access Minutes–Hours Immediate* Depends on type Third-party APIs
Refresh Token Days–Months Immediate Yes (must track) Mobile/web apps
API Key Months–Years Manual Yes (key registry) Service-to-service

*OAuth access tokens can be revoked immediately if opaque, or require blocklists if JWTs.

JWTs became popular because they eliminated the session storage bottleneck in distributed systems. But using JWTs everywhere is a mistake. If you need immediate revocation (user logout, security breach), JWTs are the wrong choice. If your token contains large payloads (extensive permissions, nested group memberships), you’ll hit HTTP header size limits. If you rotate claims frequently, stateless validation loses its advantage because you must invalidate cached public keys constantly.

The right pattern: short-lived JWT access tokens (5–15 minutes) paired with refresh tokens stored server-side. Services validate JWTs stateless. When an access token expires, the client uses the refresh token to get a new one. If you need to revoke access, you invalidate the refresh token server-side. The access token continues working until expiration, but the user cannot get a new one. Maximum revocation latency equals access token lifetime.

Access token and refresh token lifecycle:

---
config:
  look: handDrawn
---
sequenceDiagram
    participant C as Client
    participant A as Auth Service
    participant R as Resource API

    C->>A: POST /auth/login (credentials)
    A-->>C: access_token (15 min) + refresh_token (7 days)

    C->>R: GET /api/resource — Bearer access_token
    R-->>C: 200 OK

    Note over C,R: 15 minutes pass — access token expires

    C->>R: GET /api/resource (expired token)
    R-->>C: 401 Unauthorized

    C->>A: POST /auth/refresh — refresh_token
    A-->>C: new access_token + new refresh_token
    A->>A: Old refresh_token invalidated

    C->>R: GET /api/resource — new access_token
    R-->>C: 200 OK

JWT Internals — Why the Payload Is Visible

A JWT consists of three parts separated by dots: header.payload.signature. Each part is base64url-encoded JSON.

Header specifies the token type and signing algorithm:

{
  "alg": "RS256",
  "typ": "JWT"
}

Payload contains claims—statements about the user and token:

{
  "sub": "user123",
  "name": "Alice",
  "role": "admin",
  "iat": 1716672000,
  "exp": 1716675600,
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com"
}

Signature is a cryptographic hash of the header and payload using the specified algorithm:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

The signature proves the token hasn’t been tampered with. If an attacker modifies the payload (changing role from user to admin), the signature validation fails. But here’s the critical point: JWTs are signed, not encrypted. The payload is base64-encoded, which is trivial to decode. Anyone with the token can read its contents.

# Decode JWT payload
echo "eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6ImFkbWluIn0" | base64 -d
# Output: {"sub":"user123","role":"admin"}

This is intentional. JWTs are designed for authentication, not confidentiality. The signature ensures integrity (it hasn’t been modified) and authenticity (it was issued by someone with the signing key), but not secrecy. Never put sensitive data in a JWT—passwords, credit card numbers, social security numbers. These should never be in tokens at all.

Standard claims provide metadata:

  • sub (subject): User identifier
  • exp (expiration): Unix timestamp when token expires
  • iat (issued at): Unix timestamp when token was created
  • iss (issuer): Who created the token
  • aud (audience): Who the token is intended for
  • nbf (not before): Token not valid before this time

Custom claims add application-specific data like roles, permissions, and scopes. This is where JWT size becomes a problem. Each permission added to the token increases its size. A user with 50 group memberships and 200 permissions might generate a 10KB token. That’s 10KB sent in every request header. Multiply by 1000 requests per second and you’re wasting bandwidth.

Token bloat is a real issue in enterprise systems. Large organizations with complex permission models hit HTTP header limits (typically 8–16KB). The solution is to keep tokens minimal—include only user ID and a few critical claims. For detailed permissions, query a service after validating the token. This reintroduces latency, but it’s better than hitting size limits.

Another mistake: putting mutable data in JWTs. If you include email or role in the payload, what happens when the user changes their email or an admin revokes their role? The JWT remains valid until expiration, containing stale data. Short token lifespans mitigate this, but they don’t eliminate it. For critical authorization decisions, always validate against the source of truth after token validation.

Production Nightmares — Where Token Systems Break

Demonstrations of token authentication take minutes. Production token systems take months to get right. Here are the problems that emerge at scale.

Token expiration creates user frustration. Users mid-workflow suddenly get logged out because their access token expired. Implementations that don’t handle refresh gracefully force re-authentication, losing unsaved work. The fix requires background token refresh—detect expiration before it happens (check exp claim), request a new token using the refresh token, and retry the failed request transparently. Mobile apps need to handle this during network transitions and app backgrounding.

Stolen tokens grant unauthorized access. A token transmitted over HTTP (not HTTPS) can be intercepted. A token logged in application logs can be extracted. A token in client-side JavaScript can be exfiltrated via XSS. A token stored in local storage persists across sessions and survives XSS attacks. The mitigation: HTTPS everywhere, HTTP-only cookies for web, secure storage on mobile, never log tokens, and implement token binding where possible.

Refresh token abuse is worse than access token theft. A stolen access token grants access for minutes. A stolen refresh token grants access for weeks or months. Attackers can generate new access tokens indefinitely. Detection requires tracking refresh token usage patterns—multiple refresh requests from different IPs, unusual geographic locations, device fingerprint changes. Automatic rotation helps: each refresh token is single-use, and using it returns a new refresh token. If both the legitimate user and attacker try to refresh simultaneously, the conflict is detectable.

Token revocation in stateless systems is impossible by design. JWTs are stateless—no database lookup required. But this means you cannot revoke them before expiration. If a user reports their token stolen, you cannot invalidate it without maintaining a blocklist of revoked tokens. This reintroduces the state you tried to eliminate. The workaround: short token lifespans (5–15 minutes), so revocation latency is bounded, paired with revocable refresh tokens stored server-side.

Secret rotation breaks active sessions. Signing keys must be rotated periodically—quarterly or when compromised. But if you rotate the key, all existing JWTs become invalid because signature verification fails. Users are logged out en masse. The solution: support multiple signing keys simultaneously. Sign new tokens with the current key. Validate tokens using current and previous keys for a grace period (hours to days). This allows gradual rollover without service disruption.

Microservices token forwarding creates security and operational headaches. Service A calls Service B, which calls Service C. Should the original user token be forwarded through all hops? Forwarding preserves user identity, but it also grants every intermediate service the full permissions of the user. If Service B is compromised, the attacker gets user tokens. The alternative is token exchange: Service B requests a new token scoped to its interaction with Service C, carrying user context but limited permissions. This is OAuth “On-Behalf-Of” flow, and it’s complex to implement correctly.

API gateway authentication centralization creates a single point of failure. Gateways validate tokens once at the edge, then forward requests to backend services. This reduces validation overhead but creates a trust boundary—backend services trust that the gateway performed authentication. If an attacker bypasses the gateway (misconfigured network policy, internal network access), they reach services that assume authentication already happened. Defense in depth requires services to validate tokens even behind the gateway.

Machine-to-machine authentication is different from user authentication. Services authenticating other services should not use user tokens. They need service accounts with their own credentials. OAuth client credentials flow solves this, but it requires token management across dozens or hundreds of services—each needs its credentials, rotation schedules, and access policies. Secret management systems (Vault, AWS Secrets Manager, Kubernetes secrets) become critical infrastructure.

Token forwarding across trust boundaries is dangerous. Internal tokens should not be sent to external services. External tokens should not grant access to internal resources. But developers copy tokens between systems, or proxy requests without sanitizing headers. An internal JWT ends up sent to a third-party analytics service. Now that service has credentials to your system. Strict header filtering at boundaries is essential.

Workload identity in cloud-native systems is underutilized. Cloud platforms provide instance identity tokens—credentials automatically available to workloads without manual key distribution. AWS has IAM roles for EC2 and ECS. Kubernetes has ServiceAccounts with projected tokens. GCP has workload identity federation. These eliminate the need for long-lived API keys in configuration. But many systems ignore them and continue managing secrets manually, increasing risk.

Authentication is easy in demos but difficult in distributed systems because the problem space explodes. You’re not just validating one token—you’re validating thousands per second, across dozens of services, with varying trust levels, rotating keys, revoking compromised credentials, auditing access, and maintaining identity context through complex call chains. Every decision has security and operational trade-offs.

Managing Tokens at Scale — Lessons from Production

Experience teaches that token management is a continuous operational concern, not a one-time implementation.

Short-lived access tokens with refresh tokens is the standard pattern. Access tokens expire in 5–15 minutes. Refresh tokens expire in days or weeks. Clients request new access tokens automatically before expiration. This bounds revocation latency (maximum 15 minutes to fully revoke access) while minimizing re-authentication friction. The shorter the access token lifetime, the faster revocation works, but the more frequently clients must refresh.

Refresh token rotation on every use prevents abuse. When a client uses a refresh token to get a new access token, the response includes a new refresh token. The old refresh token is invalidated. If both the legitimate user and an attacker try to refresh, the first succeeds and the second fails. This signals compromise. Revoke all tokens for that user and force re-authentication. Single-use refresh tokens are harder to implement but significantly improve security.

How rotation detects a stolen refresh token:

---
config:
  look: handDrawn
---
sequenceDiagram
    participant U as User
    participant A as Auth Service
    participant E as Attacker

    Note over U,E: Attacker steals refresh_token_v1

    U->>A: POST /auth/refresh (refresh_token_v1)
    A-->>U: new access_token + refresh_token_v2
    A->>A: refresh_token_v1 invalidated

    E->>A: POST /auth/refresh (refresh_token_v1)
    A->>A: Already used — reuse detected!
    A-->>E: 401 Token Reuse
    A->>A: Revoke ALL sessions for this user
    A-->>U: Force re-authentication

Secure storage is non-negotiable. For web apps, use HTTP-only cookies for tokens to prevent JavaScript access. For mobile apps, use platform keychains (iOS Keychain, Android Keystore). For server-to-server, use secret management systems, not environment variables or config files committed to git. For client-side apps that need JavaScript access to tokens, understand that XSS is your biggest risk. No storage mechanism is safe if XSS is possible.

HTTPS everywhere is mandatory. Tokens transmitted over HTTP are trivially intercepted. This includes internal service-to-service communication. “Internal networks are trusted” is a false assumption. Compromise of one service should not grant access to all traffic. Mutual TLS (mTLS) between services adds defense in depth—both client and server authenticate via certificates.

Token scopes and least privilege reduce blast radius. A token should grant only the permissions needed for its use case. A read-only token should not allow writes. A token for accessing user profile data should not access payment methods. OAuth scopes provide this, but many systems ignore them and issue tokens with full permissions. Fine-grained scopes are harder to manage but critical for security.

Centralized token issuance with distributed validation scales. One authentication service issues tokens. All other services validate them independently using shared public keys (for JWTs) or token introspection (for opaque tokens). This avoids authentication becoming a bottleneck. But key distribution becomes a new problem—how do services get the public keys, and how do you rotate them without downtime?

Token audit logging is required for security and compliance. Log token issuance (who, when, with what scopes), token usage (which services validated which tokens), and token revocation (why was it invalidated). These logs are essential for incident response (“when did the attacker get a token?”) and compliance audits (“who accessed this patient record?”). But logs contain sensitive data. Protect them accordingly.

Rate limiting prevents token abuse. Even valid tokens can be abused—an attacker with a stolen token can issue millions of requests. Rate limiting at both the user level (X requests per user per hour) and token level (X requests per token per minute) prevents this. Distributed rate limiting becomes complex in microservices—shared state (Redis) or eventual consistency (local limits with cross-service aggregation).

Token introspection endpoints provide real-time validation. For opaque tokens, services call an introspection endpoint to validate tokens and retrieve metadata (user ID, scopes, expiration). This is slower than stateless JWT validation but enables immediate revocation. The introspection endpoint becomes critical infrastructure—if it’s down, all services lose authentication. Caching introspection results with short TTLs (30–60 seconds) reduces load while maintaining near-real-time revocation.

Monitoring token health prevents silent failures. Track token expiration rates, refresh failures, validation errors, and signature verification failures. A spike in validation errors might indicate an attacker trying modified tokens. A spike in refresh failures might indicate a key rotation gone wrong. These metrics surface problems before users complain.

Modern Authentication Architecture

Real-world systems combine multiple patterns to balance security, performance, and operational complexity.

OAuth 2.0 and OpenID Connect are industry standards. OAuth 2.0 handles authorization (granting access to resources). OpenID Connect (OIDC) extends OAuth to handle authentication (proving identity). Using these protocols means interoperability with identity providers (Okta, Auth0, Azure Entra ID, Google, GitHub) and well-tested libraries. Implementing custom authentication is risky—protocol details matter, and subtle mistakes create vulnerabilities.

Identity providers centralize authentication. Instead of every application managing passwords, an identity provider handles authentication. Applications trust the identity provider and accept its tokens. This enables single sign-on (SSO)—one login grants access to multiple applications. It also centralizes security—password policies, MFA, breach detection, and compliance happen in one place rather than being reimplemented in every app.

OAuth 2.0 / OIDC ecosystem — how identity providers manage token issuance and validation:

architecture-beta
  group clients(logos:programming-browser)[Clients]
  group authzone(logos:security-shield-network)[Auth Layer]
  group apis(logos:server-api-cloud)[Resource APIs]
  group infra(logos:database)[Infrastructure]

  service browser(logos:worldwide-web-browser)[Web App] in clients
  service mobile(logos:responsive-design-monitor-phone)[Mobile App] in clients

  service idp(logos:security-it-service)[Identity Provider] in authzone
  service gateway(logos:network-router-signal-1)[API Gateway] in authzone

  service svc1(logos:terminal)[Orders API] in apis
  service svc2(logos:terminal)[Users API] in apis

  service tokendb(logos:database)[Token Store] in infra
  service keystore(logos:security-shield-wall)[Key Store] in infra

  browser:R --> L:idp
  mobile:R --> L:idp
  idp:R --> L:gateway
  idp:B --> T:tokendb
  idp:B --> T:keystore
  gateway:R --> L:svc1
  gateway:R --> L:svc2
  svc1:B --> T:keystore
  svc2:B --> T:keystore

API gateways enforce authentication at the edge. Gateways sit between clients and backend services, validating tokens before forwarding requests. This offloads authentication from services, which can trust that authenticated requests already passed validation. But it requires defense in depth—services should still validate tokens, assuming the gateway might be bypassed.

Zero trust architecture assumes breach. Traditional security assumes the network is trusted. Zero trust assumes every request might be an attacker. Every service validates tokens. Every communication is encrypted. No implicit trust based on network location. This is the right model for cloud-native systems where network boundaries are fluid.

Workload identity simplifies service-to-service authentication. Cloud platforms provide identity tokens automatically to running workloads. Kubernetes ServiceAccounts, AWS IAM roles for pods, and GCP workload identity federation eliminate manual secret distribution. Services authenticate using platform-provided credentials that rotate automatically. This reduces operational burden and security risk.

The architecture pattern that works: centralized authentication (identity provider), distributed validation (each service validates tokens), short-lived access tokens (5–15 minutes), revocable refresh tokens (stored server-side), fine-grained scopes (least privilege), and defense in depth (multiple layers of validation).

Closing Thoughts

Tokens are conceptually simple—credentials that prove identity without revealing secrets. But managing identity and trust in distributed systems is one of the hardest engineering problems. The token itself is the easy part. The hard parts are key rotation without downtime, revocation with bounded latency, secure storage across platforms, token forwarding through microservices, detecting stolen credentials, maintaining audit trails, and balancing security with operational complexity.

Every authentication decision is a trade-off. Stateless JWTs scale well but cannot be revoked immediately. Session tokens enable instant revocation but require shared state. Short-lived tokens improve security but increase refresh overhead. Long-lived tokens reduce friction but increase compromise risk. There is no perfect solution—only informed choices about which trade-offs match your threat model and operational constraints.

The systems that get this right treat authentication as continuous infrastructure, not a feature implemented once. They monitor token health. They practice key rotation. They test revocation flows. They audit access. They assume tokens will be stolen and design for containment. Authentication demos take minutes. Production authentication systems take years to mature.

Questions

  1. Why are stateless JWTs fundamentally incompatible with immediate token revocation, and what architectural patterns address this without reintroducing centralized session state?

  2. If a refresh token is stolen, what signals or heuristics can detect the compromise before the attacker fully exploits it, and how does token rotation help?

$ share this post