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:
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
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
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:
- Subdomain (✅ Current)
- Path-Based (❌ Rejected)
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
Pros:
- Single domain
- Shorter URLs
Cons:
- Complex Load Balancer path rewriting
- Cloud Storage bucket issues (XML listings)
- Not industry standard
- Difficult to scale
- All services coupled to one domain
URLs:
https://forge.precisionsiteservices.com/
https://forge.precisionsiteservices.com/bids/
Architecture Decision Record
Read the full ADR: ARCHITECTURE_DECISION_SUBDOMAIN_ROUTING.md
User Experience
Users interact with the Portal, not individual subdomains:
- Bookmark:
forge.precisionsiteservices.com(one URL to remember) - Login: Enter credentials once
- Navigate: Click app tiles in Portal
- Seamless: SuperTokens session cookies work across all apps
- 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):
# Point to same Load Balancer IP
inventory.precisionsiteservices.com A 34.160.139.39
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
gcloud storage buckets create gs://forge-475221-inventory \
--location=us-south1 \
--uniform-bucket-level-access
- hosts:
- inventory.precisionsiteservices.com
pathMatcher: inventory-matcher
Deploy backend to Cloud Run, frontend to Cloud Storage.
Add "Inventory" tile to Portal app launcher.
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.