Architecture · Codebase Analysis
Merge auth-backend into backend
The two services are already one system split across two processes, with a bidirectional runtime dependency, a shared secret, and no real security boundary. The split costs latency on every request and has already caused production bugs.
What auth-backend owns
Auth-backend is a thin Hono service wrapping better-auth. It manages 7 tables in a separate boo_auth Postgres database: users, sessions, accounts (OAuth/OTP), verifications, organizations, members, and invitations.
It handles login flows (email OTP, Google OAuth), session management, org CRUD, membership, invitations, and a permissions middleware. It also exposes internal routes consumed by boo-backend for user/org lookups and mutations.
Notably, auth-backend does not own email sending. It calls back to boo-backend's internal email endpoint to send OTPs and invitation emails, creating a circular dependency.
How they're coupled
Request flow: every authenticated API call
| Coupling point | Impact |
|---|---|
| Session validation - every auth'd request | Network hop, no caching, 5-20ms per call |
| Auth proxy - all frontend /api/auth/* calls | Double hop: browser → backend → auth → backend → browser |
| Bidirectional dependency | Auth needs backend for email; backend needs auth for everything |
| Shared INTERNAL_SECRET | Must match across both, negates security boundary |
| Duplicated types | Permission, OrgRole, actionPolicies copy-pasted in both repos |
| Separate databases | No cross-DB FKs; org IDs stored as plain text in 15+ tables |
The security boundary is already broken
The split looks like security isolation, but boo-backend already has full access to auth-backend through the shared INTERNAL_SECRET and 40 internal API call sites. It forwards raw session cookies via the auth proxy. A compromised boo-backend can call any internal route on auth-backend, including user and org mutations.
INTERNAL_SECRETshared across both services, no mTLSauth-proxy.tsforwards all/api/auth/*calls with raw cookies- Internal routes accept boo-backend's
x-internal-actor: automationheader for full CRUD - No audit boundary, no separate access logs, no compliance framework requires it
Real cost of the split
Performance
- Every authenticated request pays a network hop (5-20ms within Railway)
- No session caching on the hot path
- A dashboard page load with 10 API calls = 10 extra round trips
Operations
- Two repos, two deploys, two migration sets
- Deployment ordering matters (contract changes)
- 12+ env vars to coordinate
- 6 test files mock
authServiceCall
Already caused a production bug
After the database was split, boo-backend kept reading org slugs from a stale local copy of the organizations table. Branded-domain provisioning silently broke for 6 orgs over 3 weeks before anyone noticed.
Arguments for keeping the split (and why they don't hold)
| Argument | Counter |
|---|---|
| Security isolation | INTERNAL_SECRET is shared. boo-backend has full internal API access. The boundary is porous. |
| Independent scaling | Current scale doesn't need it. The network hop costs more than any scaling gain. |
| Blast radius containment | Bidirectional dependency means either outage takes down both. |
| Compliance separation | No SOC 2 / HIPAA requirement in the codebase. Shared secret and implicit trust wouldn't satisfy one anyway. |
Merge plan
Move better-auth setup into boo-backend as src/lib/auth.ts. It becomes the session provider, called directly instead of over HTTP.
Unify the database. Move auth-backend's 7 tables into the boo_api database. Real foreign keys between auth and business tables become possible.
Replace resolveSession() HTTP call with a direct auth.api.getSession() function call. Eliminates the per-request network hop.
Replace all 40 authServiceCall() sites with direct function calls or Drizzle queries against the now-local schema.
Delete auth-proxy.ts and mount better-auth's handler directly at /api/auth/*. Frontend auth calls go from double-hop to direct.
Merge permission systems into one policies.ts. Delete duplicated types, policies, and the getActionPermissions() copy.
Clean up env vars. Delete INTERNAL_SECRET, AUTH_SERVICE_URL, INTERNAL_API_URL. Absorb BETTER_AUTH_SECRET and GOOGLE_CLIENT_* into boo-backend.
Analysis based on full source review of both codebases as of October 2026. auth-backend: 2,204 lines across 12 TypeScript files. boo-backend: ~42,000 lines across 90+ files. Cross-service calls counted by grep of authServiceCall, AUTH_SERVICE_URL, and fetch patterns in production source (tests excluded from the count).