Building a SaaS with OwlAuth
OwlAuth is a self-hosted authentication service, not a hosted multi-tenant control product. It does not provide organizations, tenant roles, customer API keys, subscriptions, billing, fleet placement, or tenant-scoped Control credentials.
You can nevertheless use ordinary OwlAuth deployments as the authentication cells behind a SaaS product. Your service owns tenant identity, authorization, commercial policy, and orchestration; OwlAuth continues to own Project users, login, sessions, tokens, provider configuration, and signing keys.
Integration guide, not a built-in mode
This chapter describes an architecture you can build around OwlAuth. The repository does not ship a hosted SaaS service, tenant API or CLI client, tenant console, billing system, or managed-cell orchestrator. Do not expose OwlAuth's deployment operator key to customers.
System boundary
A production design normally has three distinct authorities:
- your SaaS control service, with its own database for accounts, organizations, memberships, roles, subscriptions, and managed-resource ownership;
- an optional, isolated platform identity deployment used to authenticate people entering your SaaS console;
- one or more managed OwlAuth cells that serve customer Projects.
The SaaS service is the tenant policy-enforcement point. It calls only published OwlAuth Control APIs; it must not import owlauth-server, share its repositories, or read or write an OwlAuth database directly.
A managed cell is one OwlAuth administrative trust domain. It includes an owlauth-server deployment, PostgreSQL, a separately preserved software custody root or custom-provider authority, Auth ingress for both public Runtime and backend-only Server API routes, plus private Control ingress, and one deployment operator key. A cell can hold Projects for several organizations only when your trusted SaaS service is the sole operator.
Own traffic governance at the SaaS ingress
OwlAuth Core does not provide deployment-wide IP, route, Project, global, bot/risk, traffic-shaping, or commercial quota enforcement. A managed SaaS must apply those controls at its trusted edge or ingress, where it has the correct network, tenant, plan, and fleet context.
| Control | Owning layer |
|---|---|
| IP/network abuse, bot detection, reputation, and traffic shaping | edge/WAF/Auth ingress |
| Per-route, per-tenant, global, and fleet capacity quotas | SaaS gateway or Auth ingress |
| Subscription, entitlement, and billable usage limits | SaaS policy and metering authority |
| Request bytes, deadlines, accepted connections, and in-flight work | ingress plus OwlAuth local transport bounds |
| PostgreSQL pool and provider/worker concurrency | OwlAuth local resource-safety bounds |
| OTP attempts, generation/proof lifecycle, recipient side-effect suppression, active mail backlog safety | OwlAuth PostgreSQL protocol authority |
Core's declared 408 request_timeout means that the local listener deadline expired while waiting for in-flight capacity or running the handler. It is not a rate-limit response and does not prove that a dispatched mutation had no effect; official Runtime SDKs therefore quarantine ambiguous handoff, refresh, and logout state. Ingress traffic policy may return 429, but that is a SaaS contract rather than a Core OpenAPI guarantee. Official Runtime SDKs recognize the optional extension only when it has the exact closed Runtime error envelope with code set to rate_limited, a bounded safe message and request_id, plus exactly one decimal-seconds Retry-After value in 0..=86400; other gateway or WAF 429 shapes remain conservative protocol or indeterminate failures. Apply the denial before forwarding any Core authority work, keep its dimensions free of raw credentials and email addresses, and do not use an ingress allow decision to authenticate a Project, Application, server key, session, or proof.
Core identity semantics still fail closed independently of ingress policy. In particular, removing or resetting an edge quota cannot reset OTP attempts, bypass generation policy, revive an older challenge, or repeat a consumed proof. Passwordless email also serializes a Project-wide side-effect decision in PostgreSQL: a recent actual enqueue to the same canonical recipient or the hard active-outbox safety bound can commit the real newest generation as terminal without creating mail, while returning the same generic accepted response. That mechanism protects protocol side effects and enumeration safety; it is not IP/route admission, plan quota, billing policy, or a Core 429. Conversely, an edge denial must not mutate those PostgreSQL state machines.
Keep identities separate
The same person can have several unrelated identities:
| Identity | Meaning | Authority |
|---|---|---|
| SaaS account | person allowed to authenticate to your management product | platform identity plus current SaaS account state |
| Organization member | SaaS account with current roles in one organization | your SaaS database |
| Service account | non-human principal belonging to one organization | your SaaS database |
| Project user | end user authenticating to one customer Project | the assigned OwlAuth cell |
A Project user must never gain organization administration rights merely because an email address, provider account, or display name matches a SaaS account. Authentication proves a subject; your SaaS database remains authoritative for current membership, role, resource ownership, and commercial state.
Data ownership
Keep one clear authority for each fact:
| Concern | Authority |
|---|---|
| Accounts, organizations, memberships, invitations, roles, and service accounts | SaaS database |
| Customer API-key digests, scopes, expiry, and revocation | SaaS database |
| Subscription, entitlement, quota, and billing interpretation | SaaS database and the explicitly reconciled payment-provider contract |
| Cell placement and organization-to-Project registry | SaaS database |
| Project users, identities, applications, providers, sessions, tokens, and signing keys | assigned OwlAuth cell |
OwlAuth Project belongs_to | checked copy of an external organization identifier, never ownership authority by itself |
| External actor attribution | SaaS audit record |
| Accepted OwlAuth Control action | OwlAuth audit record with the fixed deployment_operator actor |
An organization may own several managed Projects. A managed Project should have one stable organization owner and cell assignment in your registry. Treat cell migration as an explicit migration product because issuer URLs, callbacks, keys, sessions, provider secrets, and recovery authority are deployment-sensitive.
Tenant authorization gateway
OwlAuth Control accepts one OWLAUTH_CONTROL_API_KEY and grants it full authority over the deployment. It does not attenuate that key by organization, role, Project, or belongs_to. Your gateway must perform every narrower authorization decision before using it.
For each customer management request:
- parse and bound the request;
- authenticate exactly one SaaS account or service account;
- resolve current principal and organization status;
- resolve current membership or service-account grants;
- authorize one concrete product permission;
- resolve the target managed resource through your registry;
- verify that the target belongs to the authorized organization;
- evaluate current entitlement, lifecycle state, and revisions;
- commit a durable actor-bound operation before any external side effect;
- map the operation to a closed, typed OwlAuth Control command;
- call the trusted cell Control origin with its operator key;
- finalize or reconcile the original operation and preserve correlation across both audit streams.
Never expose a generic Control proxy, arbitrary path and body forwarding, caller-selected cell origins, or raw OwlAuth Project IDs that bypass organization-qualified lookup. A valid operator key would make any such mistake deployment-wide.
Use belongs_to only as a consistency check
When provisioning a managed Project, set its OwlAuth belongs_to metadata to your organization's stable opaque public identifier. Store the returned OwlAuth Project IDs and metadata revision in your own managed-Project registry.
Before sensitive Project-bound mutations, resolve the Project from your registry and verify the exact current belongs_to and metadata revision. A mismatch should:
- fail the customer operation closed;
- create a security and reconciliation signal;
- prevent best-effort mutation;
- require repair from authoritative registry and OwlAuth state.
Caller-supplied belongs_to is never proof of ownership. Changing it alone is not a safe organization-transfer workflow.
Provision Projects with a durable saga
Your SaaS database and OwlAuth PostgreSQL cannot share one transaction. Provisioning therefore needs an explicit, idempotent saga:
Derive a globally unique Control idempotency key from the durable SaaS operation ID; do not forward a customer idempotency value directly into OwlAuth's deployment-wide namespace. If the external result is ambiguous, reconcile the same operation using the retained idempotent result or authoritative reads. Do not create a replacement Project merely because a timeout occurred.
Useful managed-resource states include provisioning, active, updating, suspending, disabled, provisioning_failed, and reconciliation_required. Protect those transitions with your own monotonic revision independently of OwlAuth's metadata revision.
Keep Auth off the management critical path
Customer applications and end users should use the assigned OwlAuth Auth endpoint's Runtime routes directly. Customer backends should use the same Auth endpoint's Server API routes with a Project server key for user-directory reads, exact lookup, Application projection reads, and online token introspection. A healthy Auth request should not synchronously depend on:
- your SaaS API or console;
- platform identity;
- the payment provider;
- the cell's Control listener;
- a fleet reconciliation worker.
If you place a global Runtime edge in front of cells, it needs an authoritative and safely cached Project-to-cell routing design. Public Project identifiers must be fleet-unique when routing by Project ID alone; otherwise include an authoritative cell or region namespace. Do not turn the Runtime edge into a synchronous tenant-RBAC or billing dependency.
Credential boundaries
Use different credentials and secret namespaces for each trust domain:
| Credential | Accepted by | Meaning |
|---|---|---|
| Platform identity credential | SaaS API | authenticated management subject only |
| Customer API key | SaaS API | SaaS principal plus a scope ceiling |
| Cell operator API key | one managed cell Control listener | full deployment Control authority |
| Project server key | Server API routes on one cell's Auth | one Project's backend directory/introspection authority |
| Application publishable key | managed Runtime | public Application identification |
| Project access or refresh token | managed Runtime/customer backend | Project user and Application session context |
A customer API key must never be forwarded to OwlAuth. A cell operator key must never appear in customer responses, browsers, tenant records, logs, traces, metrics, support bundles, or agent context. Project server keys are one-time Control reveals that must be acknowledged only after durable external secret-manager storage; they belong exclusively in customer backend custody and never in browsers, Runtime SDK configuration, URLs, or frontend bundles. Use one operator key per cell to limit blast radius, and keep Control ingress private even though network position does not replace Bearer authentication.
If you issue customer API keys, store only a versioned digest and safe lookup metadata after one-time secret display. Effective permission should be the intersection of the key's immutable scope ceiling and the principal's current permissions. Revoking membership or disabling the principal must therefore remove authority immediately.
Cells, failures, and recovery
A cell is the unit of administrative trust, capacity, backup, recovery, and incident blast radius. Keep Platform Identity and managed customer cells separate in production, including PostgreSQL authority, operator keys, signer namespaces, secret stores, and recovery paths.
| Failure | Management behavior | Customer Runtime behavior |
|---|---|---|
| SaaS database unavailable | fail tenant management closed | continue from cell authority |
| SaaS API unavailable | console and automation unavailable | continue |
| Platform identity unavailable | new administrator login affected | continue |
| Cell Control unavailable | affected commands fail or reconcile | Auth continues if its dependencies are healthy |
| Cell Auth unavailable | backend and browser Auth requests fail | affected cell Auth unavailable |
| Cell PostgreSQL unavailable | affected cell unavailable | affected cell unavailable |
| Operator-key mismatch | Control calls fail until rotation/reconciliation | Runtime and Server credentials remain independent |
| Payment provider unavailable | preserve bounded last-confirmed commercial state and reconcile | continue |
Bound work per cell with deadlines, concurrency limits, circuit breakers, and queues so one unhealthy cell cannot exhaust fleet control capacity. Persist actor attribution and operation intent before workers perform an external effect. Revalidate organization, managed-resource, entitlement, and OwlAuth revisions immediately before that effect.
Back up the SaaS registry, platform identity deployment, and each managed cell as separate authorities. After restoring different authorities from different points in time, fail ownership-sensitive management closed until the registry and OwlAuth Project metadata are reconciled. Never repair ownership automatically from ambiguous evidence.
Billing and metering
Prefer initial billing models derived from SaaS-authoritative management state, such as active managed Projects, configured Applications, administrator seats, enabled features, region, or dedicated-cell class. They avoid placing billing instrumentation in the authentication hot path.
Do not bill from ordinary logs, metrics, or security audit events unless you define a durable meter contract covering:
- the exact qualifying event;
- organization and Project attribution;
- stable idempotency identity;
- event and receipt time semantics;
- replay, late arrival, correction, and backfill;
- retention and privacy;
- completeness and reconciliation;
- behavior during source or billing outages.
Runtime-volume billing such as monthly active users or successful authentications requires an explicit durable aggregate or outbox contract. Authentication must not wait synchronously for the SaaS billing service or payment provider. Subscription cancellation should follow deliberate grace, notification, suspension, recovery, export, and retention policy rather than allowing a payment webhook to disable OwlAuth directly.
Recommended implementation order
- Build organization, membership, role, and managed-Project registry authority.
- Add a narrow typed gateway for the minimum OwlAuth Control operations you expose.
- Implement actor-bound durable operations, idempotent provisioning, and reconciliation.
- Isolate cell operator keys and private Control networking.
- Route customer Runtime traffic independently from management availability.
- Add service accounts and customer API keys only when automation needs them.
- Start commercial enforcement from management-owned resource limits.
- Add Runtime-derived meters only after defining and testing a durable measurement contract.
- Add dedicated cells, regions, or migration only when product requirements justify their operational cost.
Security checklist
Before serving multiple organizations, verify at least:
- cross-organization resource and child-ID substitution is denied;
- caller-supplied cell, Project, and
belongs_tovalues cannot override registry resolution; - stale SaaS and OwlAuth revisions fail closed;
- API-key scope ceilings intersect current principal permissions;
- disabled accounts, service accounts, organizations, and credentials lose authority;
- no endpoint behaves as a generic Control proxy;
- operator keys are redacted and unique per cell;
- failed and ambiguous provisioning is reconciled without duplicate resources;
- platform identity and managed customer cells are operationally isolated;
- Auth remains independent from SaaS, Control, and billing outages;
- restore-time ownership drift blocks mutation until reconciliation.
OwlAuth's own test suite provides evidence for the specifically exercised Project-isolation and server invariants. It does not certify a deployment or prove the tenant isolation, billing correctness, fleet orchestration, or cross-system recovery of the SaaS layer you build around it.