Refresh Token Rotation: How CDN Edge Nodes Detect Token Hijacking
Refresh tokens are the long-lived credentials that keep users authenticated without requiring repeated logins. In a CDN architecture where token-based authentication happens at the edge, a stolen refresh token gives an attacker persistent access to protected resources across every edge node in the network. Refresh token rotation solves this by ensuring that every refresh operation invalidates the previous token and issues a new one, creating a chain of single-use credentials. When an attacker attempts to replay a rotated token, the authorization server detects the reuse and revokes the entire token family — cutting off both the attacker and the compromised session. This article examines how rotation works at scale, how CDN edge proxies implement family-based revocation, and what usage patterns reveal hijacking attempts before they cause damage.
Why Static Refresh Tokens Are Dangerous in Distributed Systems
Traditional OAuth 2.0 deployments issue a single refresh token that remains valid until it expires or is explicitly revoked. In a centralized application server, this model is manageable because the server controls all token exchanges. But in a distributed CDN architecture where authentication decisions happen at dozens or hundreds of edge nodes, a static refresh token becomes a high-value target with a massive blast radius.
Consider the attack surface. A refresh token intercepted through a compromised client, a cross-site scripting vulnerability, or a man-in-the-middle attack on a poorly configured TLS connection gives the attacker the ability to generate new access tokens indefinitely. The attacker can hit any edge node in the CDN network, and because the refresh token never changes, there is no mechanism to distinguish the attacker's requests from the legitimate user's requests. The token works from Frankfurt, Tokyo, and Sao Paulo simultaneously — a usage pattern that should be impossible for a single user but is invisible without rotation.
The 2023 OAuth 2.0 Security Best Current Practice (RFC 9700) now recommends refresh token rotation for all OAuth deployments, upgrading it from a suggested practice to a strong recommendation. For CDN edge deployments where tokens traverse untrusted networks between clients and globally distributed edge compute nodes, rotation is not optional — it is the minimum viable security posture.
How Refresh Token Rotation Creates a Chain of Trust
Refresh token rotation transforms a static credential into a single-use chain. Each time a client presents a refresh token to obtain a new access token, the authorization server performs three operations simultaneously: it issues a new access token, issues a new refresh token, and invalidates the old refresh token. The new refresh token inherits the same authorization scope and user identity but carries a different opaque value and an incremented generation counter.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
This chain creates a powerful invariant: at any point in time, exactly one valid refresh token exists for each authorization grant. If the authorization server receives a refresh request with a token that has already been rotated out, it means either the legitimate client or an attacker is using an old token. In either case, the entire token family must be revoked because the chain of trust has been broken.
The Token Family Model
A token family is the set of all refresh tokens descended from a single authorization code exchange or initial credential grant. Each family is identified by a unique family ID (typically a UUID) that is embedded in every refresh token issued within that lineage. The authorization server maintains a mapping of family IDs to their current generation number and the hash of the currently valid refresh token.
-- Token family schema for distributed edge auth
CREATE TABLE token_families (
family_id UUID PRIMARY KEY,
user_id UUID NOT NULL,
client_id VARCHAR(128) NOT NULL,
current_gen INTEGER NOT NULL DEFAULT 1,
current_rt_hash VARCHAR(64) NOT NULL,
scope TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
last_rotated_at TIMESTAMP NOT NULL DEFAULT NOW(),
revoked BOOLEAN NOT NULL DEFAULT FALSE,
revoked_reason VARCHAR(64),
max_lifetime INTERVAL DEFAULT '30 days',
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Index for fast lookup during token refresh
CREATE INDEX idx_family_lookup
ON token_families (current_rt_hash)
WHERE revoked = FALSE;
When the server processes a refresh request, it hashes the incoming token and looks up the family. If the hash matches current_rt_hash, the rotation proceeds normally. If the hash matches a previous generation (tracked in an audit log), the server sets revoked = TRUE with revoked_reason = 'reuse_detected' and returns an invalid_grant error. This is the core of automatic reuse detection.
Implementing Rotation at CDN Edge Proxies
Deploying refresh token rotation at CDN edge nodes introduces challenges that do not exist in centralized deployments. Edge nodes in a globally distributed network must coordinate token state across potentially hundreds of locations, each processing authentication requests independently. The fundamental problem is consistency: when an edge node in London rotates a token, the edge node in Singapore must learn about the rotation before the old token arrives there.
Architecture Patterns for Edge Token State
Three primary patterns have emerged for managing token rotation state across edge functions and proxy layers.
Centralized token store with edge caching. Edge nodes act as proxies that forward refresh requests to a centralized authorization service. The edge caches the resulting access token locally but does not store refresh token state. This pattern is simplest to implement and provides strong consistency, but it adds latency for every refresh operation because the request must travel to the central store. For applications where refresh frequency is low (every 15 to 60 minutes), the latency penalty is acceptable.
Distributed token store with eventual consistency. Edge nodes share token state through a distributed data store such as Redis Cluster, Apache Cassandra, or a purpose-built key-value store replicated across edge locations. Token rotations are written locally and propagated asynchronously. This pattern offers lower latency but introduces a consistency window during which the old token might still be accepted at a remote node. A grace period of 2 to 5 seconds accommodates propagation delay while still detecting replay attacks that occur outside this window.
Edge-local rotation with async reconciliation. Each edge node maintains its own token rotation state and periodically reconciles with a central authority. This offers the lowest latency but the weakest consistency guarantees. It works for deployments with predictable user-to-edge affinity (enforced through consistent hashing or geographic routing), where a user's refresh requests consistently hit the same edge node.
Edge Proxy Configuration
The following configuration demonstrates how an edge proxy intercepts refresh token requests, performs rotation through the distributed store, and handles edge cases like grace periods and reuse detection. This pattern integrates with the origin protection layer to ensure that only validated refresh flows reach the authorization server.
# Edge proxy token rotation configuration
# Integrates with distributed Redis token store
upstream auth_server {
server auth-primary.internal:8443;
server auth-secondary.internal:8443 backup;
keepalive 32;
}
map $request_uri $is_token_endpoint {
~^/oauth/token 1;
default 0;
}
# Token rotation handler at edge
location /oauth/token {
# Only process refresh_token grant type at edge
if ($arg_grant_type != "refresh_token") {
proxy_pass https://auth_server;
break;
}
# Rate limit refresh attempts per client
limit_req zone=token_refresh burst=5 nodelay;
# Forward to edge rotation module
# Module checks distributed store before proxying
access_by_lua_block {
local token_store = require("edge.token_store")
local rt_hash = ngx.sha1(ngx.var.arg_refresh_token)
local family = token_store.lookup(rt_hash)
if not family then
-- Unknown token: forward to origin auth server
return
end
if family.revoked then
ngx.status = 400
ngx.say('{"error":"invalid_grant",',
'"error_description":"token family revoked"}')
return ngx.exit(400)
end
if family.current_hash ~= rt_hash then
-- Reuse detected: revoke family
token_store.revoke_family(family.family_id,
"reuse_detected")
-- Log for monitoring pipeline
ngx.log(ngx.WARN,
"TOKEN_REUSE family=", family.family_id,
" gen_presented=", family.presented_gen,
" gen_current=", family.current_gen,
" client_ip=", ngx.var.remote_addr)
ngx.status = 400
ngx.say('{"error":"invalid_grant",',
'"error_description":"token reuse detected"}')
return ngx.exit(400)
end
-- Valid token: proceed with rotation
ngx.ctx.token_family = family
}
proxy_pass https://auth_server;
# Post-rotation: update distributed store with new RT
header_filter_by_lua_block {
if ngx.status == 200 and ngx.ctx.token_family then
local token_store = require("edge.token_store")
-- New RT hash extracted from response
-- Store propagates to other edge nodes
token_store.rotate(ngx.ctx.token_family.family_id)
end
}
}
This configuration integrates with the broader zero-trust security model where every request is authenticated at the edge before reaching the origin. The edge proxy handles the fast-path rejection of revoked and reused tokens locally, only forwarding valid refresh requests to the centralized authorization server.
Detecting Token Hijacking Through Usage Patterns
Refresh token rotation provides binary reuse detection — either a token has been rotated out or it has not. But sophisticated attackers may use a stolen token before the legitimate client does, racing the rotation. To detect these scenarios, CDN edge nodes must analyze usage patterns that indicate hijacking even when rotation has not yet been triggered.
Geographic Velocity Checks
The most reliable hijacking signal is impossible geographic velocity. If a refresh token is used from a London IP address and then from a Tokyo IP address five seconds later, no legitimate user could have traveled that distance. Edge nodes log the geographic coordinates of each refresh operation and calculate the required travel speed between consecutive uses. Any velocity exceeding 1,000 km/h (approximately the speed of the fastest commercial aircraft) triggers an alert and optionally an immediate family revocation.
# Geo-velocity check for token usage anomalies
# Runs at edge before processing refresh request
import math
from datetime import datetime, timezone
def check_geo_velocity(family_id: str,
current_lat: float,
current_lon: float,
store) -> dict:
"""
Check if token usage violates geographic velocity limits.
Returns {allowed: bool, velocity_kmh: float, risk: str}
"""
last_use = store.get_last_usage(family_id)
if not last_use:
return {"allowed": True, "velocity_kmh": 0,
"risk": "none"}
# Haversine distance calculation
R = 6371 # Earth radius in km
lat1, lon1 = math.radians(last_use["lat"]), \
math.radians(last_use["lon"])
lat2, lon2 = math.radians(current_lat), \
math.radians(current_lon)
dlat = lat2 - lat1
dlon = lon2 - lon1
a = (math.sin(dlat / 2) ** 2 +
math.cos(lat1) * math.cos(lat2) *
math.sin(dlon / 2) ** 2)
distance_km = R * 2 * math.atan2(
math.sqrt(a), math.sqrt(1 - a))
# Time difference in hours
now = datetime.now(timezone.utc)
time_diff_h = (now - last_use["timestamp"]
).total_seconds() / 3600
if time_diff_h == 0:
velocity = float("inf")
else:
velocity = distance_km / time_diff_h
# Thresholds
if velocity > 1000: # Faster than commercial flight
return {"allowed": False,
"velocity_kmh": round(velocity, 1),
"risk": "critical"}
elif velocity > 500: # Suspicious but possible
return {"allowed": True,
"velocity_kmh": round(velocity, 1),
"risk": "elevated"}
else:
return {"allowed": True,
"velocity_kmh": round(velocity, 1),
"risk": "none"}
Geographic velocity checks complement the reuse detection built into rotation. While rotation catches replay after the legitimate client has rotated the token, velocity checks catch scenarios where the attacker races ahead of the legitimate client. Together, they form a layered detection system that feeds into the broader monitoring and observability pipeline.
Concurrent Session Detection
Another strong hijacking signal is concurrent refresh activity from different network fingerprints. A single user session should produce a predictable pattern: one refresh operation every N minutes from a consistent IP range, user-agent string, and TLS fingerprint. When the same token family shows refresh activity from two distinct fingerprints within a short window, it suggests that two parties hold credentials from the same grant.
Edge nodes track a session fingerprint that combines the client's ASN (Autonomous System Number), security headers like Sec-CH-UA, and the JA3 TLS fingerprint. A change in any of these components during a refresh operation does not necessarily indicate hijacking — users switch networks, update browsers, and use VPNs. But a simultaneous divergence, where two fingerprints are active within the same token family at the same time, crosses the threshold from normal behavior change to likely compromise.
Frequency Anomaly Detection
Legitimate clients refresh tokens at predictable intervals dictated by the access token lifetime. If the access token expires every 15 minutes, healthy clients will refresh roughly every 14 to 15 minutes. An attacker who has stolen a refresh token will often refresh more frequently — either because they are testing the token, because their tooling does not respect the access token lifetime, or because they are attempting to accumulate access tokens for parallel use.
Edge nodes maintain a rolling window of refresh timestamps per family and flag families where the refresh frequency deviates significantly from the expected interval. A family that suddenly shows two refreshes within 30 seconds when the access token lifetime is 15 minutes warrants investigation through the incident response pipeline.
Grace Periods and the Consistency Window Problem
The hardest engineering challenge in edge-deployed rotation is the consistency window. When edge node A rotates a token and writes the new state to the distributed store, there is a non-zero propagation delay before edge node B sees the update. During this window, the old token is technically still valid at edge node B. An attacker who intercepts the old token during this window can replay it at a different edge node before the revocation propagates.
The standard solution is a grace period: after rotation, the old token remains accepted for a configurable window (typically 2 to 5 seconds) but is flagged as consumed. If the old token is presented during the grace period, the edge node accepts it but does not perform a second rotation. Instead, it returns the same new access token and new refresh token that were issued during the original rotation. This idempotent behavior handles legitimate retries (network timeouts, client-side race conditions) without creating security gaps.
# Grace period configuration for edge token rotation
token_rotation:
# Time window after rotation during which old RT
# is still accepted (handles propagation latency)
grace_period_seconds: 3
# Maximum token family lifetime from initial grant
# Forces re-authentication regardless of rotation
max_family_lifetime_days: 30
# Maximum number of rotations per family
# Prevents infinite token chains
max_generations: 2880 # ~30 days at 15-min intervals
# Reuse detection action
on_reuse_detected: revoke_family # or: alert_only
# Concurrent session policy
concurrent_session_action: challenge
concurrent_session_threshold: 2
# Geo-velocity settings
geo_velocity:
max_speed_kmh: 1000
action: revoke_and_alert
cooldown_minutes: 5
# Distributed store settings
store:
type: redis_cluster
nodes:
- redis-eu.edge.internal:6379
- redis-us.edge.internal:6379
- redis-apac.edge.internal:6379
replication_timeout_ms: 2000
read_preference: local_then_nearest
The grace period introduces a controlled trade-off between security and usability. A shorter grace period reduces the window for replay attacks but increases the risk of legitimate requests being rejected due to propagation delays. A longer grace period is more forgiving but gives attackers more time to exploit stolen tokens. The optimal value depends on the measured replication latency of the distributed token store and the network transport characteristics between edge regions.
Integrating Rotation with Broader Edge Security
Refresh token rotation does not operate in isolation. It works alongside other authentication and authorization mechanisms at the CDN edge to create a zero-trust authentication posture. The rotation module feeds its detection signals into the same pipeline that processes WAF events, rate limiting triggers, and mTLS certificate validation results.
When the token rotation module detects reuse, it does not simply revoke the token family. It emits a structured security event that the correlation engine uses to adjust the risk score for the associated user, client, and IP address. A reuse event combined with a WAF anomaly triggers an immediate block. A geo-velocity alert alone triggers a step-up challenge through the multi-factor authentication system. This adaptive response prevents both false positives (locking out legitimate users who changed networks) and false negatives (allowing sophisticated attackers who avoid obvious signals).
Token Lifetime and Rotation Limits
Even with rotation, refresh token families must have finite lifetimes. An infinitely rotating token chain creates a session that never expires, which violates the principle that credential windows should be bounded. Best practice sets a maximum family lifetime (typically 7 to 30 days) after which the user must re-authenticate regardless of rotation state, and a maximum generation count that caps the number of rotations.
The relationship between access token lifetime and rotation frequency directly affects both security and performance. Short access token lifetimes (5 minutes) force more frequent rotations, increasing the load on the distributed token store but limiting the window during which a stolen access token is valid. Longer lifetimes (60 minutes) reduce rotation load but extend the access token exposure window. For CDN edge deployments serving API traffic, the sweet spot typically falls between 10 and 20 minutes, balancing the edge cache efficiency for dynamic content against the security requirements of the API gateway.
The JWT security best practices for distributed systems recommend embedding the token family ID and generation number as claims within the access token itself. This allows edge nodes to detect some anomalies without querying the token store — for example, an access token with a generation number lower than the generation tracked in the local cache indicates that the token was minted from an older refresh token, which may signal replay. Combined with OAuth 2.0 token exchange at the edge, this approach enables edge-local validation while maintaining the centralized rotation authority.
Production Monitoring and Alerting
A production deployment of refresh token rotation at CDN edge nodes must instrument several key metrics. Reuse detection rate tracks the percentage of refresh requests that trigger family revocation — a sudden spike indicates either an active attack campaign or a misconfigured client. Rotation latency measures the time between the old token being invalidated and the new token being propagated to all edge nodes — this must stay below the grace period to prevent false positives. Grace period utilization tracks how often old tokens are accepted within the grace window, indicating whether the window is sized correctly for the network's propagation characteristics.
These metrics feed into the observability platform alongside standard CDN metrics. Dashboard panels correlate reuse events with geographic location, client fingerprint, and time of day. Alert thresholds trigger incident response workflows when reuse rates exceed baseline, when rotation latency approaches the grace period, or when concurrent session counts spike for individual token families. The compliance team uses these dashboards to demonstrate that token lifecycle management meets regulatory requirements for credential rotation and breach detection.