Olympus Docs
CookbookMulti-tenant

White-label SaaS for end-customers

Your B2B customers ship Olympus auth to THEIR customers under their brand

You're a B2B SaaS that resells / embeds Olympus. Your customers want to brand the auth as theirs, not yours, when their end-users see it.

Layers

B2B customer = your direct customer.
End-user = your customer's customer.

B2B customer brands login page → end-user sees the B2B customer's brand.

Pattern A: per-tenant CIAM stack

Each B2B customer gets their own Olympus deployment (separate domain, separate config):

  • auth.acme-corp.com (their brand) → Acme's Olympus instance.
  • auth.bigcorp.com (their brand) → BigCorp's Olympus instance.

You operate but they brand. Highest isolation, highest cost.

See Multi-tenant hard isolation.

Pattern B: one CIAM, per-tenant subdomain

acme.your-domain.com → tenant=acme, branded as Acme.
bigcorp.your-domain.com → tenant=bigcorp, branded as BigCorp.

Shared infrastructure, per-tenant config.

Routing

Caddy:

*.your-domain.com {
  reverse_proxy hera:3000
}

Hera reads subdomain via host header:

// hera middleware
const subdomain = req.headers.host?.split(".")[0];
const tenant = await getTenantBySubdomain(subdomain);
if (!tenant) return notFound();
req.tenant = tenant;

Branding

In each page, use tenant context for theme:

export async function generateMetadata({ params }) {
  const tenant = await getTenant(params.subdomain);
  return {
    title: `Sign in - ${tenant.name}`,
    icons: { icon: tenant.favicon },
  };
}

export default function Login() {
  const tenant = useTenant();
  return (
    <div style={{ backgroundColor: tenant.primaryColor }}>
      <img src={tenant.logo} />
      <h1>Sign in to {tenant.name}</h1>
      ...
    </div>
  );
}

CSS variables:

:root {
  --color-primary: var(--tenant-primary, #4F46E5);
  --color-bg: var(--tenant-bg, #FFFFFF);
}

Set via inline style on <html> with tenant values.

Pattern C: full white-label, no Olympus mention

For deepest white-label: end-users never see "Olympus" anywhere:

  • No "Powered by Olympus" footer.
  • No links to Olympus docs.
  • No Olympus error messages.
  • Custom user-facing domain.

This requires:

  • Custom Hera build per tenant (or runtime config).
  • Stripped branding.
  • Tenant's privacy policy / TOS displayed.
  • Customer support routes to tenant's support, not yours.

Custom domain

Customer wants auth.acme.com (their domain) not acme.your-domain.com.

DNS: customer adds CNAME → your Caddy.

Caddy auto-provisions Let's Encrypt cert for that domain.

auth.acme.com {
  reverse_proxy hera:3000
}

auth.bigcorp.com {
  reverse_proxy hera:3000
}

Or wildcard with SNI-based routing.

Cert provisioning

For dozens of customer domains, Caddy can use on-demand TLS:

{
  on_demand_tls {
    ask http://your-backend/check-domain
    interval 2m
    burst 5
  }
}

:443 {
  tls {
    on_demand
  }
  reverse_proxy hera:3000
}

When a request comes in for an unfamiliar domain, Caddy asks your backend "should I issue a cert?", backend looks up if it's a registered customer domain. Yes → Caddy provisions.

End-user privacy policy

End-user signs up at auth.acme.com. The privacy policy they're agreeing to is Acme's, not yours (typically).

Display:

  • Tenant's privacy policy at registration.
  • Tenant's TOS.

Athena lets each tenant configure these URLs.

Data ownership

Critical: end-user data belongs to the B2B customer (Acme), NOT to you.

Your TOS with B2B customers should clarify:

  • B2B customer is the "data controller."
  • You're the "data processor."
  • Data belongs to B2B customer.
  • Acme can export at any time.
  • On contract end, you delete or return.

DPA (Data Processing Agreement) signed by both parties.

Identifying tenant in audit

Every audit event scoped to tenant:

INSERT INTO security_audit (tenant_id, identity_id, event_type, ...)
VALUES ('acme', $user, 'login', ...);

When investigating, scoped by tenant.

Cross-tenant ops

Your internal ops (you = the SaaS operator):

  • Can access any tenant's data (for support).
  • Logs every cross-tenant action.
  • Customer can audit (you provide audit log to them on request).

Beyond support, NO cross-tenant operations. Don't aggregate end-user data across tenants for analytics, privacy violation.

Disaster recovery per tenant

If one tenant's data is corrupted, restore that tenant's portion without affecting others. Per-tenant backups (or tagged within shared DB).

End-user vs B2B billing

End-users don't pay you (they pay the B2B customer if at all). B2B customer pays you (based on MAU, features, etc.).

Stripe integration: customer side only.

Onboarding new tenants

UI for B2B customer onboarding:

  1. Sign up (you, as direct customer, have your own auth).
  2. Create tenant.
  3. Configure branding (upload logo, set colors).
  4. Configure their subdomain or custom domain.
  5. Verify domain (DNS TXT).
  6. Test login.
  7. Go live.

Self-service for low-touch, sales-led for enterprise.

On this page