Skip to main content

Subdomain-Based Routing

ForgeX uses subdomain-based routing to provide true microservices isolation with shared authentication. This architecture decision record explains why and how.

Architecture Overview

forge.precisionsiteservices.com     → Portal (authentication & app launcher)
bids.precisionsiteservices.com → Bids Service
projects.precisionsiteservices.com → Projects Service
field.precisionsiteservices.com → Field Service

Each subdomain points to the same Load Balancer IP (34.160.139.39), which routes requests based on the Host header.

Why Subdomain Routing?

Industry Standard

Major SaaS platforms use subdomain architecture:

🎬

Netflix

www.netflix.com / api.netflix.com

💳

Stripe

dashboard.stripe.com / api.stripe.com

💬

Slack

app.slack.com / api.slack.com

🐙

GitHub

github.com / api.github.com

This pattern is battle-tested at scale and well-understood by developers.

Technical Benefits

🛡️True Service Isolation

Each service is independently deployable:

  • Deploy Bids service without affecting Projects
  • Scale services independently based on load
  • Monitor each service separately
  • Different resource limits per service
# Deploy only Bids service
gcloud run deploy forge-bids-backend --region us-south1

# Projects service unaffected
☁️Simplified Infrastructure

Cloud Storage buckets serve index.html naturally:

  • No path rewriting required
  • No complex Load Balancer path rules
  • Each bucket maps to one subdomain
gs://forge-475221-portal  → forge.precisionsiteservices.com
gs://forge-475221-bids → bids.precisionsiteservices.com
🔒Better Security

Subdomain boundaries provide natural security isolation:

  • XSS Isolation: Compromise in one service doesn't directly affect others
  • Independent CSP: Each service has its own Content Security Policy
  • Cookie Scope: Cookies can be service-specific or shared (.precisionsiteservices.com)
  • CORS Control: Fine-grained cross-origin rules
🚀Future-Proof

Architecture scales without breaking changes:

  • Add new services by adding DNS records
  • Spin off services to separate infrastructure
  • Sell or transfer services by changing DNS
  • Per-service SSL certificates if needed

Authentication Flow

Subdomain routing enables a clean authentication pattern via SuperTokens:

🔍 Click diagram to expand
info

Key Insight: SuperTokens session cookies set with domain=.precisionsiteservices.com are automatically sent to all subdomains. Users authenticate once at the Portal via SuperTokens, then seamlessly access all services without re-authentication.

Security Implementation

HTTP-Only Cookies

res.cookie('accessToken', accessToken, {
httpOnly: true, // Prevents XSS access
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
domain: '.precisionsiteservices.com', // Works across all subdomains
maxAge: 15 * 60 * 1000 // 15 minutes
});

Security Headers

Each service sets appropriate security headers:

Content-Security-Policy: default-src 'self' *.precisionsiteservices.com
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff

CORS Configuration

Backend services whitelist specific subdomains:

const allowedOrigins = [
'https://forge.precisionsiteservices.com',
'https://bids.precisionsiteservices.com',
'https://projects.precisionsiteservices.com',
'https://field.precisionsiteservices.com'
];

app.use(cors({
origin: (origin, callback) => {
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true // Allow cookies
}));

Infrastructure Configuration

DNS Setup

Four A records pointing to the same Load Balancer IP:

forge.precisionsiteservices.com     A  34.160.139.39
bids.precisionsiteservices.com A 34.160.139.39
projects.precisionsiteservices.com A 34.160.139.39
field.precisionsiteservices.com A 34.160.139.39
info

All subdomains point to the same IP. The Load Balancer routes based on the Host header.

SSL Certificate

Managed SSL certificate includes all subdomains:

gcloud compute ssl-certificates create forge-ssl-cert \
--domains=forge.precisionsiteservices.com,bids.precisionsiteservices.com,projects.precisionsiteservices.com,field.precisionsiteservices.com

Google automatically renews the certificate before expiration.

Load Balancer URL Map

Host-based routing rules:

hostRules:
- hosts:
- forge.precisionsiteservices.com
pathMatcher: portal-matcher

- hosts:
- bids.precisionsiteservices.com
pathMatcher: bids-matcher

- hosts:
- projects.precisionsiteservices.com
pathMatcher: projects-matcher

- hosts:
- field.precisionsiteservices.com
pathMatcher: field-matcher

pathMatchers:
- name: portal-matcher
defaultService: backend-bucket-portal

- name: bids-matcher
defaultService: backend-bucket-bids
pathRules:
- paths: ['/api/*']
service: backend-service-bids
tip

Host rules are simpler and more reliable than path rules. Each host gets a dedicated path matcher.

vs Path-Based Routing

We initially considered path-based routing but switched to subdomains:

Pros:

  • Industry standard
  • True service isolation
  • Simpler infrastructure
  • Better security boundaries
  • Future-proof

Cons:

  • Multiple DNS records (minimal)
  • Longer URLs (mitigated by Portal)

URLs:

https://forge.precisionsiteservices.com
https://bids.precisionsiteservices.com
📖

Architecture Decision Record

Read the full ADR: ARCHITECTURE_DECISION_SUBDOMAIN_ROUTING.md

User Experience

Users interact with the Portal, not individual subdomains:

  1. Bookmark: forge.precisionsiteservices.com (one URL to remember)
  2. Login: Enter credentials once
  3. Navigate: Click app tiles in Portal
  4. Seamless: SuperTokens session cookies work across all apps
  5. Direct Access: Can bookmark individual apps (e.g., bids.precisionsiteservices.com)

The subdomain architecture is invisible to users while providing technical benefits.

Adding New Services

To add a new service (e.g., Inventory):

1
Add DNS Record
# Point to same Load Balancer IP
inventory.precisionsiteservices.com A 34.160.139.39
2
Update SSL Certificate
gcloud compute ssl-certificates create forge-ssl-cert-v2 \
--domains=forge.precisionsiteservices.com,...,inventory.precisionsiteservices.com

# Update Load Balancer to use new cert
gcloud compute target-https-proxies update forge-https-proxy \
--ssl-certificates=forge-ssl-cert-v2
3
Create Cloud Storage Bucket
gcloud storage buckets create gs://forge-475221-inventory \
--location=us-south1 \
--uniform-bucket-level-access
4
Add Load Balancer Host Rule
- hosts:
- inventory.precisionsiteservices.com
pathMatcher: inventory-matcher
5
Deploy Service

Deploy backend to Cloud Run, frontend to Cloud Storage.

6
Add to Portal

Add "Inventory" tile to Portal app launcher.

info

Existing services are unaffected when adding new ones. Deploy with confidence!

Troubleshooting

Cookies not working across subdomains

Verify the cookie domain is set correctly:

// ✅ Correct (works on all subdomains)
domain: '.precisionsiteservices.com'

// ❌ Wrong (only works on exact domain)
domain: 'forge.precisionsiteservices.com'

Check in browser DevTools → Application → Cookies.

CORS errors between services

Ensure all subdomains are in the CORS whitelist:

const allowedOrigins = [
'https://forge.precisionsiteservices.com',
'https://bids.precisionsiteservices.com',
// Add all subdomains!
];

Check browser console for specific origin that was rejected.

Load Balancer routing to wrong service

Verify host rules are configured correctly:

gcloud compute url-maps describe forge-lb --format=yaml

Check the Host header matches your subdomain exactly.

SSL certificate errors

Ensure all subdomains are in the certificate:

gcloud compute ssl-certificates describe forge-ssl-cert \
--format="value(managed.domains)"

If missing, create a new certificate with all domains and update the proxy.

Best Practices

🚪

Portal-First Design

Users should always enter through the Portal, not directly to services. This ensures:

  • Centralized authentication
  • Consistent branding
  • Role-based app access
  • Analytics on app usage
🔒

Cookie Security

Always use httpOnly, secure, and sameSite flags:

{ httpOnly: true, secure: true, sameSite: 'strict' }

This prevents XSS and CSRF attacks.

CORS Whitelist

Never use origin: '*' in production. Whitelist specific subdomains only.

🚀

Service Independence

Each service should be deployable independently. Avoid tight coupling between services.

Next Steps