Architecture Overview
High-Level Architecture
Public Traffic
┌─────────────┐ ┌─────────────────────────┐ ┌────────────────────┐
│ Browser │ ──▶ │ Cloudflare Zero Trust │ ──▶ │ Vercel (Next.js) │
│ (Public) │ │ │ │ │
│ │ │ • Access authentication │ │ • proxy.ts │
│ │ │ • Origin secret header │ │ • Origin validation│
└─────────────┘ └─────────────────────────┘ └────────────────────┘
Local Traffic
┌─────────────┐
│ Browser │ ──▶ Vercel directly
│ (Local) │ (localhost / LAN / Tailscale)
│ │
│ │ proxy.ts bypasses authentication
└─────────────┘
Security Layers
The application uses two independent layers of protection:
Cloudflare Zero Trust - Authenticates visitors before requests ever reach Vercel. - Prevents unauthorized users from accessing the application.
Origin Secret Verification (proxy.ts)
- Cryptographically verifies that requests actually passed through Cloudflare.
- Prevents direct access to the Vercel deployment.
- Blocks forged Host headers and header spoofing attacks.
Domain & DNS
| Purpose | Domain | Target |
|---|---|---|
| Public application | home.parthjain.tech |
Cloudflare DNS → Vercel |
| Default Vercel deployment | personal-dashboard-*.vercel.app |
Redirected by proxy.ts |
| Local development | localhost:3000 |
Direct access |
[!IMPORTANT] The DNS record for
home.parthjain.techmust be proxied (orange cloud enabled) so Cloudflare Zero Trust policies are enforced.
Cloudflare Zero Trust Configuration
Access Application
Create a Self-hosted Access application.
| Setting | Value |
|---|---|
| Type | Self-hosted |
| Domain | home.parthjain.tech |
| Access Policy | Allow only your identity (email, One-Time PIN, etc.) |
Transform Rule — Origin Secret Injection
This rule prevents attackers from bypassing Cloudflare and connecting directly to Vercel.
Location
Rule
| Setting | Value |
|---|---|
| Rule Name | Inject Vercel Origin Secret |
| Condition | Hostname equals home.parthjain.tech |
| Action | Set static request header |
| Header Name | X-Vercel-Origin-Secret |
| Header Value | Same value as X_VERCEL_ORIGIN_SECRET in Vercel |
[!IMPORTANT] Cloudflare does not allow custom headers beginning with
cf-. UseX-Vercel-Origin-Secretinstead.proxy.tsaccepts both hyphenated and underscored header names for compatibility.
Vercel Configuration
Production Environment Variables
| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
INTERVALS_API_KEY |
Intervals.icu API key |
INTERVALS_ATHLETE_ID |
Athlete ID (0 = authenticated user) |
X_VERCEL_ORIGIN_SECRET |
Shared origin verification secret |
CF_ACCESS_CLIENT_ID |
(Optional) Cloudflare Access service token |
CF_ACCESS_CLIENT_SECRET |
(Optional) Cloudflare Access service token secret |
UPTIME_KUMA_URL |
Prometheus endpoint |
UPTIME_KUMA_API_KEY |
API key (used as HTTP Basic password) |
TELEMETRY_URL |
Host telemetry endpoint |
Deployment Protection
Vercel's built-in production deployment protection requires: - Vercel Pro - Advanced Deployment Protection (~$150/month add-on)
On the Hobby plan it only protects Preview Deployments, not Production. Because of this limitation, Cloudflare Zero Trust is used instead, providing enterprise-grade authentication at no additional cost.
proxy.ts
Location
[!NOTE] Next.js 16 renamed
middleware.tstoproxy.ts. The exported function isproxy(), notmiddleware().
Request Flow
Incoming Request
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Is the hostname local? │
│ │
│ localhost │
│ 127.0.0.1 │
│ *.local │
│ LAN IPs │
│ Tailscale │
│ Device hostnames │
│ │
│ YES → Allow request │
└──────────────────────────────────────────────────────────────┘
│ NO
▼
┌──────────────────────────────────────────────────────────────┐
│ Is hostname home.parthjain.tech? │
│ │
│ NO → Redirect (307) │
│ https://home.parthjain.tech/... │
└──────────────────────────────────────────────────────────────┘
│ YES
▼
┌──────────────────────────────────────────────────────────────┐
│ Is an origin secret configured? │
│ │
│ NO → Allow request │
│ (graceful setup/development mode) │
│ │
│ YES → Validate request header │
│ │
│ Match → Allow │
│ Mismatch → 403 Forbidden │
└──────────────────────────────────────────────────────────────┘
Accepted Request Headers
Checked in this order:
- X-Vercel-Origin-Secret
- X_Vercel_Origin_Secret
- X-Origin-Secret
- X_Origin_Secret
- X-CF-Origin-Secret
Accepted Environment Variables
Checked in this order:
- X_VERCEL_ORIGIN_SECRET
- VERCEL_ORIGIN_SECRET
- ORIGIN_SECRET
- CF_ACCESS_ORIGIN_SECRET
Matcher
Runs on every request except static assets. Excluded paths include:
- _next/static
- _next/image
- favicon.ico
- *.css, *.js, *.svg, *.png, *.jpg, *.jpeg, *.gif, *.webp, *.ico
Attack Scenarios
| Scenario | Result |
|---|---|
Visitor opens home.parthjain.tech |
Cloudflare Zero Trust login |
Visitor discovers .vercel.app URL |
Redirected to canonical domain |
Attacker spoofs Host: home.parthjain.tech |
Origin secret validation fails → 403 |
| Attacker spoofs host + secret header | Secret is unknown → 403 |
| Bot or crawler accesses deployment | Redirect or 403 |
| Access from localhost / LAN / Tailscale | Allowed immediately |
Rotating the Origin Secret
If the shared secret is ever compromised:
1. Generate a new random secret.
2. Update both locations:
- Vercel: X_VERCEL_ORIGIN_SECRET
- Cloudflare Transform Rule: X-Vercel-Origin-Secret
3. Redeploy the Vercel application so the updated environment variable takes effect.
Security Summary
| Layer | Purpose |
|---|---|
| Cloudflare Zero Trust | User authentication at the edge |
| Transform Rule | Injects trusted origin header |
proxy.ts |
Validates origin secret |
| Canonical Redirect | Prevents direct .vercel.app usage |
| Local Bypass | Preserves localhost/LAN/Tailscale development workflow |