Skip to content

Design Principles

P1. Identity and commerce are separate concerns, one process

Section titled “P1. Identity and commerce are separate concerns, one process”

Identity answers “who you are.” Commerce answers “what you bought.” What you can do is answered by each application. These three are separate concerns, but they live in the same saas-core process — not separate microservices. The module boundaries (identity/SSO/commerce) are the split points when scale demands it.

P2. Better Auth is the identity kernel, not a custom IdP

Section titled “P2. Better Auth is the identity kernel, not a custom IdP”

Authentication primitives (registration, login, session, OAuth, social login) are provided by Better Auth, an embedded TypeScript library. saas-core does not reimplement password hashing, session tokens, or OAuth flows. Custom IdP deployments (Logto, Keycloak) are intentionally avoided—they add deployment complexity without benefit for a single-operator product suite.

P3. saas-core is the OAuth Server. Each SaaS is an OAuth Client.

Section titled “P3. saas-core is the OAuth Server. Each SaaS is an OAuth Client.”

Every application registered in saas-core gets a unique client_id and audience. JWTs are issued with per-application audience values:

// Acme's token
{"sub":"user_abc","aud":"tobby-api","client_id":"tobby-web"}
// Beta's token
{"sub":"user_abc","aud":"studio-api","client_id":"studio-web"}

A Acme token presented to Beta is rejected at signature verification because the aud claim doesn’t match. This is the security baseline for subscription isolation.

P4. SSO = session reuse, not token sharing

Section titled “P4. SSO = session reuse, not token sharing”

A shared cookie domain (.example.com) means the browser sends the same session cookie to every subdomain. When the user visits a new application in the same SSO group, the session is immediately recognized — no redirect, no re-authentication. Applications in different SSO groups require explicit re-authentication at the authorize endpoint.

P5. Identity is universal. Commerce is per-application.

Section titled “P5. Identity is universal. Commerce is per-application.”

The user table contains no app_id — a user who registers on any application exists globally. The subscription table contains app_id on every row — Acme Pro does not entitle Beta access. This dual shape enables cross-product identity (one person, multiple products) with per-product subscription isolation.

P6. Record facts, do not interpret entitlements

Section titled “P6. Record facts, do not interpret entitlements”

saas-core stores that User X has an active subscription to tobby.pro.monthly. The mapping from SKU to feature set (1000 AI calls, 50GB storage) is the application’s responsibility. saas-core never builds an entitlements table.