Authentication Architecture
Overview
Section titled “Overview”Multi-site authentication system with one API backend serving a main site and multiple satellite sites.
Components
Section titled “Components”- Main Site:
gigmanager.local(production:example.com) - Account owner, payments, full access - Satellite Sites: Customer domains - Team members, limited permissions
- API Backend:
api.gigmanager.local(production:api.example.com) - Single source of truth - Mobile Apps: Future - Same as satellite permissions
Token Strategy
Section titled “Token Strategy”Two-Token System
Section titled “Two-Token System”-
accessToken (JWT)
- Short-lived (15 minutes)
- Sent in
Authorization: Bearerheader - Validated stateless (signature verification only)
- Contains: userId, permissions, expiry
-
sessionId (UUID)
- Long-lived (30 days)
- Stored in database/Redis
- Used only for refresh operations
- Links to user account and permissions
Storage by Site Type
Section titled “Storage by Site Type”Main Site (gigmanager.local)
Section titled “Main Site (gigmanager.local)”- accessToken: Memory (lost on page refresh)
- sessionId: httpOnly cookie (automatic, secure from XSS)
- Permissions: Full (payments, account management, team management)
- CORS:
credentials: 'include'required
Satellite Sites
Section titled “Satellite Sites”- accessToken: Memory or localStorage
- sessionId: localStorage
- Permissions: Limited (data entry, reports only - no payments/account settings)
- CORS: Headers only, no credentials
Authentication Flows
Section titled “Authentication Flows”Login - Main Site
Section titled “Login - Main Site”POST /api/loginBody: { email, password, source: "main" }Credentials: include
Server Response: Set-Cookie: sessionId=xyz; HttpOnly; Secure; SameSite=Lax Body: { accessToken }
Client Storage: accessToken → memory sessionId → automatic (cookie)Login - Satellite
Section titled “Login - Satellite”POST /api/loginBody: { email, password, source: "satellite" }
Server Response: Body: { accessToken, sessionId }
Client Storage: accessToken → localStorage or memory sessionId → localStorageRefresh - Main Site
Section titled “Refresh - Main Site”POST /api/refreshCredentials: include (sessionId cookie sent automatically)
Server: Reads req.cookies.sessionId Validates session in database Returns: { accessToken }
Client: Updates accessToken in memoryRefresh - Satellite
Section titled “Refresh - Satellite”POST /api/refreshHeader: X-Session-ID: {sessionId from localStorage}
Server: Reads req.headers['x-session-id'] Validates session in database Returns: { accessToken }
Client: Updates accessToken in localStorageNormal API Request (Both)
Section titled “Normal API Request (Both)”GET /api/dataHeader: Authorization: Bearer {accessToken}
Server: Validates JWT signature (stateless) Extracts userId/permissions from claims Processes requestPayment Operation (Main Site Only)
Section titled “Payment Operation (Main Site Only)”POST /api/subscription/createHeader: Authorization: Bearer {accessToken}Cookie: sessionId (automatic)
Server: 1. Validates accessToken 2. Checks req.cookies.sessionId exists (403 if not) 3. Validates session.source === "main" 4. Validates session.permissions.payments === true 5. Processes payment via StripeDatabase Session Structure
Section titled “Database Session Structure”{ sessionId: "uuid-v4", userId: "user_id", accountId: "main_account_id", // Links team members to paying account source: "main" | "satellite", permissions: { payments: boolean, // Main site only accountSettings: boolean, // Main site only teamManagement: boolean, // Main site only dataEntry: boolean, // Both viewReports: boolean // Both }, createdAt: timestamp, lastUsed: timestamp, expiresAt: timestamp}Security Model
Section titled “Security Model”Main Site Protection
Section titled “Main Site Protection”- httpOnly cookies prevent XSS token theft
- SameSite=Lax prevents CSRF attacks
- Payment operations require cookie (can’t be triggered from satellites)
- Re-authentication required for sensitive changes
Satellite Site Protection
Section titled “Satellite Site Protection”- Limited permissions in session (no payments, no account changes)
- Session revocation possible server-side
- XSS risk accepted (localStorage) but damage limited by permissions
- Same-origin policy prevents cross-site token theft
If Token Compromised
Section titled “If Token Compromised”Main site token (XSS on gigmanager.local):
- Attacker can use httpOnly cookie via requests (can’t steal it)
- Can perform user actions until session revoked
- Can’t exfiltrate sessionId itself
- Session can be killed server-side
Satellite token (XSS on satellite or localStorage theft):
- Attacker gains limited permissions only
- Cannot manage payments or account
- Cannot add/remove team members
- Session can be revoked server-side
Payment Flow
Section titled “Payment Flow”All payments happen on main site only.
From Satellite:
- User clicks “Subscribe” or “Upgrade”
- Redirect to main site:
https://gigmanager.local/subscribe?return={satellite-url} - User completes payment on main site (secure cookie auth)
- Redirect back to satellite site after completion
Stripe Integration:
- Main site creates Stripe Checkout Session (requires cookie auth)
- User completes payment on Stripe’s domain
- Stripe webhook notifies backend
- Backend updates subscription status
- No credit card data touches our servers
Why This Architecture
Section titled “Why This Architecture”Performance
Section titled “Performance”- 99% of requests validate JWT stateless (microsecond latency)
- Database lookup only on refresh (every 15 min per user)
- Redis session storage for fast lookups
- Horizontal scaling trivial (stateless validation)
Security
Section titled “Security”- Payment operations isolated to most secure domain
- Defense in depth (different auth methods, different permissions)
- Revocation capability when needed
- XSS damage limited by permission scoping
Flexibility
Section titled “Flexibility”- Works across unrelated domains (no CORS cookie issues)
- Mobile apps use same pattern as satellites
- Each frontend uses authentication that works for its context
- Single API serves all clients
Simplicity
Section titled “Simplicity”- Each frontend has one auth pattern (not mixing methods)
- Clear separation of concerns
- Standard patterns (JWT, httpOnly cookies, localStorage)
- Minimal CSRF concerns (SameSite + no sensitive cookies on satellites)
Production Considerations
Section titled “Production Considerations”Development
Section titled “Development”- Node.js serves HTTPS directly with local certificates
- Hosts file maps local domains
- Both tokens visible for debugging
Production
Section titled “Production”- Nginx/reverse proxy terminates HTTPS
- Node.js serves HTTP internally
- Let’s Encrypt handles certificates
- Same code, environment variable switches behavior
Session Management
Section titled “Session Management”- Redis for session storage (fast, scalable)
- Regular cleanup of expired sessions
- Session metadata for anomaly detection (IP, user agent)
- Audit log for sensitive operations
Token Lifetimes
Section titled “Token Lifetimes”- accessToken: 15 minutes (balance between performance and security)
- sessionId: 30 days (user convenience, can be shorter for high-security)
- Configurable per environment
Alternative Considered: Pure Stateless
Section titled “Alternative Considered: Pure Stateless”Not chosen because:
- Can’t revoke compromised tokens
- Can’t force logout or ban users mid-session
- Can’t update permissions without re-login
- Payment system requires revocation capability
Would be suitable for:
- Read-only applications
- No payment processing
- Acceptable to wait for token expiry on security events