Untitled
Cross-Subdomain Authentication Guide#
This document explains the architecture and implementation details for implementing secure, performant authentication across multiple subdomain projects using edge middleware and client-side data fetching.
Table of Contents#
- Architecture Overview
- Security Model
- Cookie Mechanics
- Edge Middleware Pattern
- Client-Side Data Fetching
- SWR Implementation Deep Dive
- CORS Configuration
- Complete Implementation
- Performance Characteristics
- Security Analysis
Architecture Overview#
The Pattern#
textKey Benefits#
- Fast: Edge middleware (~5ms) + static pages (CDN) + client hydration
- Cheap: No SSR costs, minimal edge function costs
- Secure: HttpOnly cookies + JWT validation + edge blocking
- Simple: Centralized auth logic, minimal per-project code
- Scalable: Static generation, CDN delivery
Security Model#
Defense in Depth#
textCookie Mechanics#
Cookie Configuration#
When the auth service sets the JWT cookie:
httpBrowser Behavior#
Cookie IS automatically sent to:
- ✅
auth.gaz.codes/api/profile - ✅
project-a.gaz.codes/api/anything - ✅
project-b.gaz.codes/api/anything - ✅ Any subdomain of
.gaz.codes
Cookie is NOT sent to:
- ❌
otherdomain.com - ❌
gaz.codes.evil.com(not a subdomain) - ❌ HTTP sites (when Secure flag is set)
JavaScript CANNOT:
- ❌ Read:
document.cookiewon't show HttpOnly cookies - ❌ Write: Cannot modify or delete HttpOnly cookies
- ❌ Send to wrong domain: Browser enforces domain restriction
JavaScript CAN:
- ✅ Trigger fetch requests that include the cookie (with
credentials: 'include') - ✅ Receive data from API that validated the cookie
Client-Side Fetch Behavior#
typescriptWhat happens:
- Browser checks: Is
auth.gaz.codessame-site asproject-a.gaz.codes? ✓ - Browser checks: Does cookie domain
.gaz.codesmatch? ✓ - Browser checks: Is connection HTTPS (Secure flag)? ✓
- Browser checks: Is SameSite policy satisfied? ✓
- Browser automatically attaches cookie to request
- JavaScript never sees the cookie value
- JavaScript receives the response data
Edge Middleware Pattern#
Shared Middleware Utility#
Create a reusable middleware factory in your shared packages:
typescriptPer-Project Implementation#
Each project uses the shared middleware with minimal configuration:
typescriptThat's it! ~10 lines per project.
Latency Characteristics#
textClient-Side Data Fetching#
Basic Pattern#
typescriptTimeline#
textBasic Hook Implementation#
typescriptSWR Implementation Deep Dive#
Why SWR?#
SWR (stale-while-revalidate) is a React hooks library for data fetching that provides:
- Automatic caching: Fetch once, use everywhere
- Deduplication: Multiple components requesting same data = single network request
- Focus revalidation: Refresh data when user returns to tab
- Automatic retries: Network failures are retried automatically
- Optimistic updates: Update UI before server confirms
- Pagination support: Built-in infinite loading
- TypeScript support: Full type safety
Installation#
bashBasic SWR Implementation#
typescriptSWR Magic: Automatic Deduplication#
typescriptHow it works:
- Component A calls
useAuth()→ SWR makes network request - Component B calls
useAuth()→ SWR returns cached data (no network request) - Component C calls
useAuth()→ SWR returns cached data (no network request) - When data updates, ALL components re-render automatically
Advanced SWR Configuration#
typescriptSWR Configuration Explained#
Deduplication#
typescript- If
useAuth()is called multiple times within 60s, only the first call makes a network request - Subsequent calls return cached data immediately
- After 60s, the next call will make a fresh request
Revalidation Strategies#
typescriptUser switches back to your tab → SWR automatically refreshes data to ensure it's current.
typescriptNetwork drops then reconnects → SWR refreshes data automatically.
typescriptIf cached data is older than dedupingInterval → SWR fetches fresh data in background.
Retry Logic#
typescriptBehavior:
textUseful for handling temporary network issues or server downtime.
Optimistic Updates#
typescriptSWR with Multiple Endpoints#
typescriptGlobal SWR Configuration#
typescriptSWR with TypeScript#
typescriptSWR Performance Comparison#
typescriptSWR Debugging#
typescriptCORS Configuration#
Your auth API must allow cross-subdomain requests:
typescriptWhy each header:
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin |
Specifies which origin can access the resource |
Access-Control-Allow-Credentials |
Allows cookies to be sent cross-origin |
Access-Control-Allow-Methods |
HTTP methods the API supports |
Access-Control-Allow-Headers |
Headers the API accepts |
Access-Control-Max-Age |
How long browser caches preflight response |
Complete Implementation#
Step 1: Install Dependencies#
bashStep 2: Create Shared JWT Utilities#
typescriptStep 3: Create Shared Auth Middleware#
typescriptStep 4: Create Shared Auth Hook with SWR#
typescriptStep 5: Implement in Projects#
typescripttypescriptStep 6: Configure CORS in Auth API#
typescriptPerformance Characteristics#
Latency Breakdown#
textTotal time to interactive: ~250ms
Compare to SSR approach:
textSSR time to interactive: ~400ms (60% slower)
Cost Analysis (Vercel Pro)#
Edge Middleware:
textSSR:
textSavings: ~94% cheaper with edge + static
Caching Benefits (with SWR)#
textSecurity Analysis#
Attack Vectors & Mitigations#
1. XSS (Cross-Site Scripting)#
Attack: Inject JavaScript to steal auth token
javascriptMitigation: HttpOnly cookie
text2. CSRF (Cross-Site Request Forgery)#
Attack: Malicious site triggers authenticated request
htmlMitigation: SameSite=Lax cookie
text3. Token Theft via MitM (Man-in-the-Middle)#
Attack: Intercept network traffic to steal token
Mitigation: Secure flag
text4. Subdomain Takeover#
Attack: Attacker gains control of subdomain (e.g., old.gaz.codes), steals cookies
Mitigation: Monitor DNS, decommission unused subdomains
text5. JWT Signature Forgery#
Attack: Craft fake JWT to impersonate user
Mitigation: Strong secret + verification
text6. Replay Attacks#
Attack: Steal valid JWT, reuse to access account
Mitigation: Short expiry + refresh tokens
textSecurity Checklist#
- ✅ Cookie has
HttpOnlyflag - ✅ Cookie has
Secureflag - ✅ Cookie has
SameSite=LaxorStrict - ✅ Cookie domain is
.gaz.codes(not broader) - ✅ JWT secret is 256+ bits, securely stored
- ✅ JWT signature verified on every request
- ✅ Access tokens expire within 1 hour
- ✅ HTTPS enforced across all subdomains
- ✅ CORS properly configured (specific origins only)
- ✅ Unused subdomains decommissioned
- ✅ Regular security audits
Troubleshooting#
Cookie Not Being Sent#
Symptom: Client-side fetch returns 401, even though user is logged in
Causes:
- Missing
credentials: 'include'in fetch - CORS not configured properly
- Cookie domain mismatch
Debug:
typescriptFix:
typescriptCORS Preflight Failures#
Symptom: OPTIONS request returns error, GET/POST never fires
Debug:
bashExpected response:
textMiddleware Redirect Loop#
Symptom: Endless redirects between project and auth
Cause: Middleware misconfigured, redirects authenticated users
Debug:
typescriptSWR Not Caching#
Symptom: Every component mount triggers new network request
Cause: SWR key is not stable (changes on every render)
Debug:
typescriptAdditional Resources#
Summary#
This architecture provides:
✅ Security: HttpOnly cookies, JWT validation, edge blocking ✅ Performance: Edge middleware (~5ms), static pages, SWR caching ✅ Cost: ~94% cheaper than SSR ✅ Developer Experience: Simple hook API, automatic caching, type safety ✅ Scalability: Static generation, CDN delivery, minimal server load
The combination of edge middleware for authentication gates and SWR for client-side data fetching provides the optimal balance of security, performance, and developer experience for cross-subdomain authentication.