prototype · access-control
Zero Trust Access Patterns
Experiments comparing practical access models for private web routes — Cloudflare Access, signed tokens, and application-layer JWT validation.
- zero-trust
- cloudflare-access
- private-routes
- security
This lab compares access control models for private web routes. The goal is operational: which approaches actually work, under what conditions, and where each one breaks.
What “private route” means in practice
A private route is one that should not be accessible to unauthenticated users. The gap between “should not be accessible” and “is not accessible” is where most access control failures live.
| Mechanism | What it actually does |
|---|---|
noindex | Asks search crawlers not to index the URL |
robots.txt | Asks compliant crawlers not to visit the path |
| Security through obscurity | Hopes attackers don’t guess the URL |
| Cloudflare Access (without origin lockdown) | Adds authentication but leaves origin reachable |
| Cloudflare Access + origin lockdown | Gates the route at the network edge |
| Application-layer JWT validation | Defense in depth, catches origin bypasses |
| Cloudflare Tunnel | Eliminates the public-facing origin entirely |
Pattern 1: Cloudflare Access with Tunnel
Strongest practical option for a small deployment. cloudflared creates an outbound-only tunnel — the origin has no public IP. All ingress flows through Cloudflare’s network after authentication.
What it protects: Unauthenticated reads, direct-to-origin attacks, port scans What it doesn’t protect: Authenticated users with excessive permissions, Cloudflare infrastructure incidents, SSRF from inside the network
Setup cost: Moderate — requires running cloudflared as a system service and configuring DNS via the Cloudflare dashboard.
Pattern 2: Cloudflare Access + firewall + JWT validation
For cases where Tunnel is not available (existing infrastructure, VPS with public IP):
- Cloudflare Access policy in front of the route
- Firewall rule allowing only Cloudflare IPs to reach port 443
- Application validates
Cf-Access-Jwt-Assertionheader
This achieves the same security posture as Tunnel with more operational surface.
Weak point: Firewall rules drift. Cloudflare’s IP ranges change. If an IP is added to Cloudflare’s range and not to your allowlist, the gap is invisible until someone exploits it. Tunnel avoids this class of drift.
Pattern 3: Signed one-time tokens
For time-limited access without adding a user to a persistent policy:
- Server generates a token: HMAC-SHA256 over
{user_id}:{resource_id}:{expiry}with a server-held secret - Token is sent to the recipient out-of-band
- Application validates signature, checks expiry, marks token as used
- Used tokens stored in a fast store (Redis, Cloudflare KV) with TTL matching expiry
Suitable for: Temporary CV access, shared document links, contractor staging access Not suitable for: Long-lived access, high-frequency use cases (token store becomes a bottleneck), scenarios where the server cannot maintain state
Experiment log
Cloudflare Access + Spring Boot JWT validation — working prototype. Filter validates aud, iss, signature, and expiry. Key rotation picked up within cache TTL (60 minutes). No issues in testing.
One-time token via Cloudflare Worker — in progress. Worker generates tokens stored in KV, validates on request, marks used. Token invalidation is atomic with KV’s compare-and-swap pattern.
Tunnel on a low-spec VPS — working. cloudflared memory footprint is modest (~50MB). DNS propagation after tunnel route dns was fast (< 2 minutes). The main operational concern is the cloudflared process dying — solved with a systemd unit with restart policy.