Skip to content

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.tech must 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

Cloudflare Dashboard
└── parthjain.tech
    └── Rules
        └── Transform Rules
            └── Modify Request Header

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-. Use X-Vercel-Origin-Secret instead. proxy.ts accepts 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

project-root/
└── proxy.ts

[!NOTE] Next.js 16 renamed middleware.ts to proxy.ts. The exported function is proxy(), not middleware().

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