27. Organizations & SSO (Team & Legal Team)

Alex hires Bea. She needs her own login for LexVault's projects — no shared passwords, no forwarded API keys.

Not the same as chapter 19 (Team Collaboration). That covers project-level members (Viewer / Developer / Admin / Owner) — one project shared with individually-invited teammates. This chapter is about organizations — a company-level container with OIDC SSO login, member roles across the whole org, and orgs owning projects. On Team you can use either or both.

What Alex is setting up

  • A LexVault organization that owns the Team project from chapter 26.
  • Google Workspace as the identity provider (any OIDC-conformant provider works — Microsoft Entra ID, Okta, Authentik, custom).
  • Bea invited to the org as a member — she'll sign in with her @lexvault.eu Google account and see the LexVault project alongside her own.

1. Create the organization

Team-tier feature. Organizations require team_beta_access on your account. Fresh accounts don't have it and won't see the Organizations entry in the sidebar or the Create organization button. Email contact@eurobase.app or use the request form on the pricing page to ask for the beta grant.
  1. Console → OrganizationsCreate organization.
  2. Give it a name ("LexVault") and submit.
  3. Alex is now the sole admin. Nothing broke on the project side — Alex still owns their existing project directly.
  4. One org per user, first release. After you create your first org, the Create organization button hides and the API refuses a second org for the same creator with 409 you already own an organization. Multi-org support (each with its own SSO) is on the roadmap; ping us on Discord if it blocks your setup.

2. Register Eurobase as an OIDC client at Google

You'll need four values from Google Cloud Console:

FieldWhat it is
Issuerhttps://accounts.google.com
Client IDFrom your OAuth client registration.
Client SecretKept only server-side, sealed with AES-256-GCM.
Redirect URLRegister exactly https://api.eurobase.app/platform/auth/sso/callback at Google.

Google Workspace walkthrough:

  1. Google Cloud Console → APIs & ServicesCredentials.
  2. Create CredentialsOAuth client ID.
  3. Application type: Web application. Name it "Eurobase".
  4. Authorised redirect URIs: https://api.eurobase.app/platform/auth/sso/callback
  5. Save. Copy the Client ID + Client Secret.
Same redirect URL for every org. The callback resolves the org from a signed state parameter, so registering /platform/auth/sso/callback once at the IdP is enough — no per-org URI edits.

3. Save the OIDC config in Eurobase

  1. Console → Organizations → LexVault → SSO panel.
  2. Fill in Provider (google), Issuer, Client ID, paste the Client Secret, and the redirect URL above.
  3. Save. The panel shows •••••••• (set) once stored. To rotate later, paste a new secret and save again — old value overwritten.

4. Invite Bea

Prerequisite. Bea must sign up as a regular Eurobase user first (Free tier is fine). The invite endpoint refuses email addresses that don't already exist in platform_users — you'll see "no platform user with that email — user must sign up first" otherwise.
  1. Bea creates a Eurobase account at console.eurobase.app with bea@lexvault.eu — the same email that lives in her Google Workspace.
  2. Alex → Organizations → LexVault → MembersAdd member → paste bea@lexvault.eu.
  3. Role: member (can't invite others or edit SSO). admin would let her manage the org config.
  4. Save.

Bea receives a notification email from Eurobase — subject "You've been added to LexVault on Eurobase" — naming Alex as the inviter and linking to the sign-in page. The DB row is written before the mail is sent, so a mail-provider hiccup never leaves her in a half-invited state.

5. Bea signs in with SSO

  1. Bea opens console.eurobase.app/login.
  2. Clicks Sign in with SSO and enters bea@lexvault.eu.
  3. Redirected to Google, completes the Workspace login, comes back to Eurobase. Backend validates the ID token against LexVault's OIDC config, checks Bea's email is in org_members, and issues a platform session.
  4. Bea lands on the projects page with LexVault's project visible.

6. Attach a project to the org

Projects attach to an org at creation time. Every project Alex creates while admin of LexVault auto-attaches to LexVault by default, and the create-project wizard shows an Owner picker (see chapter 2) so Alex can pick Personal per-project when they don't want a project to be org-visible. Any org-attached project shows up in every LexVault member's project list with an Org badge.

Org membership grants project access. Every route on an org-attached project — project middleware, storage, realtime, DDL — unions the caller's direct project_members row (chapter 19) with the org's org_members row via IsProjectAccessible. Bea sees the project in her list AND can open it in a single click — no separate per-project invite required for read access.

Role mapping (conservative by default; see chapter 19 to widen per-project):
  • Org admin → project admin. Mirrors SetProjectOrg's auth check — "admin of the org that owns the project" ≈ "admin of the project." Can manage members, save Settings, run DDL, deploy edge functions, etc.
  • Org member → project viewer (read-only). Can open the project, browse tables, read rows, read logs. Cannot run /sql writes, deploy edge functions, change schema, upload storage, manage webhooks / cron / migrations. To grant an org member write access to a specific project, add them under that project's Members tab (chapter 19) with the role you want — the effective role is the higher of the direct membership and the org-derived one.
Why viewer as the default: RequireMinRole("developer") gates arbitrary SQL, DDL, function deploy (including RLS-bypass flags), storage writes, and more. Granting all that to every org member on every org project automatically would be a much larger surface than the least-privilege intent of the org / SSO series. Widening later is easy; narrowing after users depend on writes is not.
Existing projects (created before the org): no self-serve move today. The API endpoint (PATCH /platform/projects/{id}/org) exists, but no console UI is wired to it yet. If you have a personal project that predates your org and want to move it in (or vice versa), reply to any support email or ping us on Discord — we'll flip org_id for you. A self-serve Owner control on the project settings page is tracked as a follow-up.
Pro checkout now honours the picker. The wizard's Owner choice is persisted on the checkout intent (including across the billing-profile detour) and passed to project creation in the Mollie payment webhook. Two edge cases fall back safely to personal so nobody loses money after paying: (1) target org deleted between checkout and payment confirmation, and (2) caller's admin membership revoked between the two. Both events log a distinct WARN for ops. Every plan — Free, Pro, Team, Legal Team — respects the picker.

7. Require SSO for every org project

Once SSO is configured, you can lock every project the org owns behind SSO login. Turning this on stops password sessions from reaching any project attached to the org — even for the admin who flipped the switch, until they re-sign-in via SSO.

  1. Console → Organizations → LexVault → Require SSO toggle → ON. The toggle fires the PATCH the moment you click it — there is no confirmation modal today, so the switch is live before you finish reading this sentence. Have an SSO login ready first (see the red callout below).
  2. Any subsequent request from a password session hits 403 sso_required_for_org at every org-owned access site — ListProjects filters the org's projects out; the project middleware refuses direct navigation; the org detail page refuses reads; the WebSocket realtime upgrade refuses subscribes. Enforcement is re-checked on every request, so flipping the toggle takes effect without waiting for sessions to expire.
  3. When a user's password session hits an org-owned org detail route (/platform/orgs/<id>), the console sends them to /login?sso_required_for=<org>. On project or realtime 403s the browser goes to plain /login; the user picks Sign in with SSO themselves and enters their email. Deep-link handling of the sso_required_for query param on the login page is a follow-up.
No admin exemption. The Require SSO toggle applies to every session equally — admin, member, or superadmin. If you flip it while signed in with a password, your very next request to a project belonging to the org gets 403. Have an SSO login ready before you enable it, or you'll immediately lock yourself out until you re-sign-in via SSO.
Turning it off. Same toggle — but only from an SSO-authed session for this org. The PATCH /platform/orgs/<id>/sso-required handler applies the same SSO gate to itself once sso_required is on, so a password session can't turn it back off (that's the "lock yourself out" case above — sign in via SSO first). Once flipped off, password sessions immediately regain access on the next request; no session invalidation, no re-login. Enforcement lives on the org row (organizations.sso_required) and is read fresh on every check.
SSO for org A doesn't unlock org B. If a user is a member of two orgs — both with Require SSO on — signing in via A's IdP grants access to A's projects only. B still refuses that session until the user signs in via B's IdP. Cross-org access requires SSO for each org whose Require SSO is on.

Troubleshooting

  • "no platform user with that email — user must sign up first" — the invitee hasn't created a Eurobase account yet. Ask them to sign up first, then retry the invite. The email must match exactly — case-insensitive OK, but +tag aliases like bea+eurobase@lexvault.eu won't.
  • "SSO sign-in failed" after the IdP redirect — three usual causes: (1) the returned email isn't in org_members; (2) the redirect URI at Google doesn't match the exact /platform/auth/sso/callback path; (3) the client secret in the SSO panel doesn't match the current secret at Google.
  • "SSO requires a Team-tier organization to have invited you" — the person hit Sign in with SSO but no org has invited them under that email. Add them via the members panel.

Security notes

  • Client secret is encrypted at rest with AES-256-GCM using a server-side platform key (PLATFORM_ENCRYPTION_KEY). Never persisted plaintext.
  • Access to org-owned projects is gated on org_members: an SSO login only succeeds for an email an org admin has already invited. Two properties fall out of that: (1) the load-bearing trust is on your org admins to only invite people they mean to; (2) a third-party IdP that returns a valid ID token for a user we've never invited still cannot get a platform session.
  • SSO sessions have the same lifetime as password logins (24 h access token, refresh via the standard flow).