CDN cache invalidation for auth content
When and how to purge caches
CDN caching speeds up Hera's static assets. But cached content can be stale after deploys. Invalidate carefully.
What's cached
- Hera's JS / CSS bundles (versioned URLs, auto-invalidate).
- Static images / fonts.
- HTML pages (if cached at all).
- API responses (usually NOT cached, but check).
Don't cache:
- Login pages with CSRF tokens.
- Anything personalized.
- API responses with auth.
URL versioning
Best invalidation: don't have to. Version assets:
/_next/static/abc123/main.js ← unique hash per buildNext.js does this automatically. Old version stays cached forever (no one fetches it). New version has new URL.
Cache-Control: public, max-age=31536000, immutable
API responses
Usually:
Cache-Control: no-storeAuth-related responses change per user. No caching.
For some read-only endpoints (like /well-known/openid-configuration):
Cache-Control: public, max-age=60010 min. Auto-invalidates.
When to manually purge
- Deployed wrong content (typo, broken).
- Security update that invalidates cached responses.
- Logo changed and old still served.
# Cloudflare API purge
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
-H "Authorization: Bearer $TOKEN" \
--data '{"files":["https://ciam.your-domain.com/logo.svg"]}'
# Purge everything (heavy)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
-d '{"purge_everything": true}'Don't purge everything for small changes, re-fetches all pages from origin, briefly slow.
Purge per tag
Cloudflare allows tagging:
Response: cf-cache-tag: hera-v1.4.0Purge all hera-v1.4.0:
curl -X POST .../purge_cache --data '{"tags": ["hera-v1.4.0"]}'Useful for "purge everything from this deploy."
Cache-Control directives
Cache-Control: public, max-age=3600Cached for 1 hour.
Cache-Control: public, max-age=3600, stale-while-revalidate=86400Serve stale for 24h while fetching fresh.
Cache-Control: no-cache, no-store, must-revalidateNever cache.
Cache-Control: private, max-age=0Browser cache OK; CDN no.
ETags
ETag: "abc123"Client sends If-None-Match: "abc123" on re-request. Server returns 304 Not Modified if unchanged.
Saves bandwidth. CDN respects.
Purge after key rotation
When rotating Hydra's JWKS:
- New keys appear in
/well-known/jwks.json. - CDN cache might serve old for
max-age.
Solutions:
- Short TTL:
Cache-Control: public, max-age=60. - Manual purge on rotation.
# In rotation script:
curl -X POST $CF_API/purge_cache --data '{"files":["https://ciam.../.well-known/jwks.json"]}'JWKS endpoints should have short TTL, minutes, not hours.
Versioned configuration
For settings vault changes that affect cached content:
- Make change in settings vault.
- Increment
config_versionin metadata. - Hera renders content with
?config_v=Nquery. - CDN treats as different URL → fresh cache.
Granular invalidation without explicit purge.
Webhook from CMS
If you have a CMS for Hera's brand assets:
- CMS publishes change.
- Webhook to your purge endpoint.
- Endpoint hits CDN API.
app.post("/webhooks/cms-published", async (req, res) => {
await purgeUrl(req.body.url);
res.json({ ok: true });
});Auto-purge on content change.
Soft purge
Cloudflare allows soft purge (mark stale, revalidate on next request):
curl ... --data '{"files":["..."], "soft_purge": true}'Less impact than hard purge. CDN serves stale for ~30s while fetching fresh.
Origin shield
Cloudflare Argo: requests go through one "shield" datacenter close to origin. Saves origin from being hit by all edges.
Edge 1 → Shield → Origin
Edge 2 → Shield → Origin (cache hit at shield)
Edge 3 → Shield → Origin (cache hit at shield)If hot endpoint, $5/mo for Argo saves your origin from 100x traffic.
Test caching
curl -I https://your-domain.com/static/main.js
# Look at:
# - Cache-Control header
# - CF-Cache-Status (HIT/MISS/EXPIRED)
# - Age headerVerify what you think is cached IS cached.
Mistakes
Caching sensitive data
GET /api/whoami → cachedDifferent users see same response (the first user's). Privacy disaster.
Fix: ALL API responses with auth:
Cache-Control: no-storeCaching dynamic CSP
If you per-request nonce, cached HTML has stale nonce → CSP rejects scripts.
Solutions:
- Don't cache HTML.
- Or: vary CSP by deploy version, cache for short period.