Home  /  CDN Security  /  Request Signing
CDN Security

CDN Request Signing: HMAC, JWT, and URL Token Authentication

By FlyFish NWA Editorial, Edge Computing Engineer September 2, 2026 9 min read

Unsigned CDN URLs are public by default. Anyone with the link can hotlink, share, or scrape your content. Request signing solves this by cryptographically binding each URL to an expiry, client context, and secret key — enforced in microseconds at the edge.

Modern CDNs do more than cache. They act as a distributed authorization layer, deciding on every request whether to serve, redirect, or reject. Request signing is the foundation of that layer. Unlike cookies or basic auth, signed requests work seamlessly with cache, support anonymous users, and survive across any client without headers. For premium video, private assets, software packages, and API acceleration, signing is non-negotiable.

This guide compares the three dominant patterns — HMAC signed URLs, JWT edge auth, and URL token authentication — with production-ready implementation details, trade-offs, and operational pitfalls we see at scale.

Why Request Signing Matters at the Edge

Without signing, your CDN is an open proxy to your origin. Attackers can bypass paywalls by guessing sequential URLs, embed your high-bandwidth video on their site, or brute-force private S3 origins through the CDN. Signed requests enforce three guarantees simultaneously:

Critically, validation happens before cache lookup. A valid signature results in a cache HIT or MISS; an invalid one returns a 403 at the edge without ever contacting origin. This protects origin from both abuse and cost. When combined with DDoS protection strategies, request signing dramatically reduces the attack surface for volumetric and application-layer attacks.

Signing also preserves cache efficiency. Unlike Authorization headers which often bypass cache, signed URLs are cache keys. Two users with different tokens requesting /video/1080p/segment_42.ts?exp=...&sig=... will not share cache, but users with the same signed URL will. Design your signing scope carefully to balance security and hit ratio.

1. HMAC-Based Signed URLs: Lightweight and Fast

HMAC is the workhorse for CDN URL signing. You concatenate a canonical string — usually path + expiry + optional IP + optional key ID — and sign it with HMAC-SHA256 using a symmetric secret. The signature is appended as a query parameter. Validation is a single hash comparison.

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages

HMAC is ideal when you need minimal overhead, language-agnostic verification, and maximum performance. CloudFront Signed URLs, Fastly URL signing, and Akamai Token Auth all use this primitive under the hood.

Canonical Signing Pattern

import hmac, hashlib, base64, time

SECRET = b'your-256-bit-secret'
KEY_ID = "v1"

def sign_url(path: str, expires: int, client_ip: str = "") -> str:
    # Canonical string must be deterministic and unambiguous
    data = f"{path}:{expires}:{client_ip}:{KEY_ID}".encode()
    sig = hmac.new(SECRET, data, hashlib.sha256).digest()
    token = base64.urlsafe_b64encode(sig).decode().rstrip("=")
    return f"{path}?exp={expires}&kid={KEY_ID}&sig={token}"

# Example: 5-minute URL bound to viewer IP
url = sign_url("/assets/premium/report.pdf", int(time.time())+300, "203.0.113.45")
# -> /assets/premium/report.pdf?exp=172529...&kid=v1&sig=4fX...

Edge Validation (VCL / Nginx)

# Nginx + secure_link (HMAC-SHA256)
secure_link $arg_exp,$arg_sig;
secure_link_md5 "$uri $arg_exp $remote_addr v1 your-256-bit-secret";

if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; } # expired

# VCL equivalent: recompute HMAC and compare in constant time

Key design decisions: include the key ID (kid) to support rotation, bind to IP only when necessary (it breaks NAT and mobile), and always use constant-time comparison to prevent timing attacks. Keep the canonical string format versioned. Changing it later invalidates all historical URLs.

For content where cache hit ratio is critical, review your caching strategy to ensure signed parameters are part of the cache key but not over-fragmented. Exclude the signature from cache key normalization if your CDN supports it.

2. JWT-Based Authentication at the Edge

JWTs move identity to the edge. Instead of signing just a URL, you sign a JSON payload containing claims: sub, exp, path, ip, acl, and custom entitlements. The token is passed as a query parameter, cookie, or Authorization: Bearer header and validated by the CDN using a shared secret (HS256) or public key (RS256/ES256).

JWTs shine when you already have an identity provider and need fine-grained access. A single JWT can authorize /videos/* for a subscriber tier without minting a new URL for every segment — essential for HLS/DASH where a manifest references hundreds of segments.

// Node.js: Mint JWT for CDN (HS256)
import jwt from 'jsonwebtoken';

const token = jwt.sign({
  sub: "user_8921",
  exp: Math.floor(Date.now()/1000) + 600, // 10 min
  path: "/videos/premium/*",
  ip: "203.0.113.45",
  tier: "pro"
}, SECRET, { algorithm: 'HS256', keyid: 'v1' });

// URL: https://cdn.example.com/videos/premium/intro.m3u8?token=${token}

// Edge validation (Cloudflare Workers / Fastly Compute)
const payload = await jwtVerify(token, SECRET);
if (payload.path !== requestPath && !minimatch(requestPath, payload.path)) 
  return new Response('Forbidden', {status: 403});

Use RS256 or ES256 when the CDN validates but does not mint tokens. The origin holds the private key; the edge only needs the public key, limiting blast radius if an edge node is compromised. ES256 is 40% faster to verify than RS256 at equivalent security.

Beware JWT bloat. A typical JWT is 300-600 bytes, which inflates URLs and may exceed header limits. Keep claims minimal, avoid embedding large ACLs, and prefer short-lived tokens. Always validate exp, nbf, and aud if present. For observability, log the kid and jti (JWT ID) to correlate denylists, as discussed in CDN monitoring and observability.

CDN Request Signing Flow Sequence showing client requesting signed URL from app server, then accessing CDN edge which validates signature before serving from cache or origin. Client Browser / Player App Server Signs URL / JWT CDN Edge Validates sig • Cache Origin S3 / Storage 1. GET /sign?path= 2. 302 Signed URL 3. GET /asset?exp=&sig= (or ?token=JWT) 4. MISS → Fetch Invalid / expired signature → 403 at edge (no origin fetch)
Figure 1 — Stateless validation at the edge prevents origin load and leakage. Signing happens once at the app server; verification happens on every edge PoP.

3. URL Token Authentication: CDN-Native Tokens

URL Token Authentication is a vendor-specific implementation that wraps HMAC with additional controls: path wildcards, IP CIDR, country, referer, and protocol enforcement. Akamai EdgeAuth, CloudFront Signed Cookies, and Bunny Token Authentication fall here. You generate a token that encodes policy rather than just a signature.

Example token structure (decoded): exp=1725300000~acl=/premium/*~ip=203.0.113.0/24~hmac=.... The CDN parses and enforces each field natively, without custom Worker code.

# Python: Akamai-style token
import hmac, hashlib

def akamai_token(acl, exp, ip, secret_hex):
    secret = bytes.fromhex(secret_hex)
    field = f"exp={exp}~acl={acl}~ip={ip}"
    h = hmac.new(secret, field.encode(), hashlib.sha256).hexdigest()
    return f"{field}~hmac={h}"

token = akamai_token("/premium/*", 1725300000, "203.0.113.45", "a1b2c3...")
url = f"https://cdn.example.com/premium/video.m3u8?token={token}"

URL tokens are powerful for marketing campaigns and partner distribution where you need to restrict by referer or geo without writing edge logic. The downside is vendor lock-in; token format is not portable. If you anticipate multi-CDN, abstract token generation behind an internal service that can emit different formats from one policy.

Tokens also enable signed cookies for HLS. Instead of signing every segment URL, you set a __Secure-Token cookie once after authentication; the player requests segments normally and the edge validates the cookie. This improves cache hit ratio and simplifies players.

Comparison: HMAC vs JWT vs URL Token

Criterion HMAC Signed URL JWT URL Token (Vendor)
Payload Hash only, no claims JSON claims (sub, tier, acl) Encoded policy string
Size ~45 bytes (Base64) 300–600 bytes 120–250 bytes
Validation 1x HMAC-SHA256 Base64 + JSON + verify Native CDN parser
Latency ~0.05ms ~0.2–0.5ms ~0.05ms (native)
Best For Static assets, images, downloads Video with entitlements, APIs Geo/referer-restricted campaigns
Portability High (any edge) High (standard) Low (vendor-specific)
Revocation Key rotation only jti denylist + rotation Key rotation / token blacklist

Implementation Best Practices

Getting signing right is less about cryptography and more about operational discipline. Follow these rules from production incidents we have reviewed:

1. Use Separate Keys per Environment and Scope

Never reuse the same secret for staging and production, or for video and downloads. Scope keys by purpose (video-prod-v1, assets-prod-v1) so rotation can be surgical. Store secrets in a KMS or secrets manager, not environment variables, and expose only the key ID to the edge.

2. Enforce Clock Skew and Constant-Time Checks

Allow 30–60 seconds of skew between generator and edge to handle NTP drift. Reject tokens with exp far in the future (e.g., >24h) to prevent abused long-lived URLs. Always compare signatures in constant time; naive string equality leaks timing side channels.

3. Sign the Minimal Canonical String

Include only what you need: normalized path (lowercased, decoded), expiry, and optionally IP or method. Do not sign the full query string unless you normalize sorting, or attackers can reorder parameters to bypass validation. Document the canonicalization precisely.

Security Note: Do Not Trust Client IP Alone

IP binding improves security but breaks legitimate users behind CGNAT, VPNs, or mobile handoffs. Prefer short TTL + token binding over strict IP checks for consumer content. Use IP binding for enterprise or internal distribution where IPs are stable.

4. Cache Correctly

Configure Vary and cache keys to include the token only when necessary. For HLS with JWT cookies, cache by path without the token and validate the cookie separately to maximize hits. For signed URLs, ensure the CDN caches 403 responses for only a few seconds to avoid caching a bad signature for legitimate users after rotation. See performance and security benefits of a CDN for how signing interacts with Tiered Cache and Origin Shield.

Key Rotation and Revocation Without Downtime

Rotation is where most teams fail. A hard secret change instantly invalidates all outstanding URLs, causing playback failures. Implement versioned keys:

  1. Generate new secret as v2, deploy to edge alongside v1 (edge accepts both).
  2. Update your application to sign new URLs with v2 only.
  3. Wait until max TTL of v1 URLs expires (e.g., 24h).
  4. Remove v1 from edge.

Automate this with your CI/CD and CDN API. For JWT with RS256, publish a JWKS endpoint (/.well-known/jwks.json) and let the edge fetch and cache public keys. Rotate by adding a new kid to JWKS, then retiring the old one after expiry.

For instant revocation, maintain a denylist of jti or token hashes in an edge KV store (Cloudflare Workers KV, Fastly Edge Dictionary, Akamai EdgeKV). On validation, check the denylist before checking the signature. Keep TTL on denylist entries equal to token expiry to bound storage. This pattern is essential for logout or subscription cancellation.

If you run multi-CDN, synchronize rotation across providers via a central signing service. That service becomes the single source of truth, emitting the correct token format per CDN from one policy engine, which aligns with edge computing architecture principles of pushing logic to the edge while keeping control centralized.

Monitoring and Debugging Signed Requests

Signing failures are invisible to users — they just see 403s. Instrument aggressively. Log at the edge: kid, validation result, failure reason (expired, bad sig, IP mismatch), and cache status. Export to your observability pipeline and alert on spikes in 403 rate, which often indicate clock skew or a bad deploy rather than an attack.

Provide a debug header in staging (X-Signature-Debug: exp=... computed=... received=...) but never in production. Use synthetic monitors that mint a valid URL and assert 200, plus a monitor with an intentionally invalid signature asserting 403. This catches misconfigured secrets before customers do.

Finally, treat signing as part of your security posture, not just access control. Combine it with bot management, WAF, and rate limiting for defense in depth.

When implemented correctly, HMAC gives you speed, JWT gives you identity, and URL tokens give you turnkey policy enforcement. Most mature architectures use HMAC for static assets and JWT cookies for video — getting the best of both worlds while keeping edge validation under a millisecond.

Frequently Asked Questions

What is the difference between HMAC signed URLs and JWT for CDN authentication?

HMAC signed URLs use a symmetric key to hash URL components like path and expiry, resulting in a compact ~45-byte signature. JWTs are self-contained tokens with a JSON payload that can carry user identity, roles, and ACLs. HMAC is faster and ideal for simple asset protection; JWT is better when you need rich context at the edge, such as authorizing all segments for a subscriber tier with one token.

How long should a CDN signed URL remain valid?

Use the shortest TTL that preserves UX. For paywalled video or downloads, 60–300 seconds is typical. For image previews, 5–15 minutes. For private software distribution, up to 24 hours may be acceptable if combined with IP binding. Always allow 30–60 seconds of clock skew and avoid issuing tokens valid far in the future.

Can signed URLs be revoked before expiration?

Stateless signatures cannot be revoked without state. Enable revocation by versioning keys with a key ID and rotating them to invalidate all URLs from that version, or by maintaining a denylist of token IDs (jti) in an edge KV store checked on every request. Short TTLs plus re-issuance also limit exposure without explicit revocation.

Should request signing be validated at the edge or origin?

Validate at the edge. Edge validation blocks invalid requests before they reach origin or pollute cache, reduces latency, and saves origin cost. Return 403 directly from the edge on failure. Use origin validation only as a secondary check for highly sensitive operations.

References & Standards: RFC 7234 · Cloudflare documentation