Company SSO
Let readers sign in with Google, Microsoft Entra ID, Okta, Auth0, Clerk or Keycloak, with step-by-step setup for each provider.
Each SSO provider adds a "Continue with …" button to the sign-in page. All six presets use OpenID Connect with the authorization code flow and PKCE. You register the docs as an app with the provider, then copy its client ID into the config and its secret into an environment variable.
How every provider is set up
Find your callback URL
The provider sends readers back to:
https://<docs host><basePath>/_auth/callback/<id><id> is the provider's id, which defaults to its type. Examples:
| Docs served at | Provider | Callback URL |
|---|---|---|
https://docs.acme.com | google | https://docs.acme.com/_auth/callback/google |
https://api.acme.com/docs (Nest, basePath: '/docs') | okta | https://api.acme.com/docs/_auth/callback/okta |
http://localhost:3010/docs (local test) | keycloak | http://localhost:3010/docs/_auth/callback/keycloak |
Register one callback URL per environment (production, staging, local).
Register the app with the provider
Follow the section for your provider below. You come away with a client ID and a client secret.
Add the provider to the config
access: {
groups: { staff: { domains: ['acme.com'] } },
rules: [{ path: '/internal/*', groups: ['staff'] }],
providers: [
{ type: 'google', clientId: '1234-abc.apps.googleusercontent.com', clientSecretEnv: 'GOOGLE_CLIENT_SECRET', hostedDomain: 'acme.com' },
],
},The client ID is not a secret and lives in the config. The secret never does: clientSecretEnv names the environment variable that holds it.
Set the secrets on the server
ORBITDOCS_AUTH_SECRET=<32+ random characters>
GOOGLE_CLIENT_SECRET=<client secret from the provider>The first sign-in fetches the provider's discovery document (<issuer>/.well-known/openid-configuration), so the server needs outbound HTTPS to the provider.
Options every provider takes
Prop
Type
What the server reads from the provider:
- Email: the
emailclaim, elsepreferred_username. If neither looks like an email, it calls the provider's userinfo endpoint. A reader with no email can't sign in. - Name: the
nameclaim. - Groups: the claim named by
groupsClaim(defaultgroups), as a list. It is matched againstidpGroupsin your access groups.
scopes replaces the defaults
scopes: ['groups'] requests only groups, and sign-in then fails for lack of an email. Always include openid, email and profile: scopes: ['openid', 'email', 'profile', 'groups'].
To offer two apps of the same provider (say, two Okta orgs), give them different ids. Each gets its own callback URL.
Google accounts and Google Workspace.
Create the OAuth client
- Open the Google Cloud console and pick or create a project.
- Set up the OAuth consent screen (Google Auth Platform). For Workspace-only docs, choose the Internal audience.
- Go to Clients (or APIs & Services → Credentials) and create an OAuth client ID of type Web application.
- Under Authorized redirect URIs, add
https://docs.acme.com/_auth/callback/google. - Copy the client ID and the client secret.
Configure
providers: [
{
type: 'google',
clientId: '1234-abc.apps.googleusercontent.com',
clientSecretEnv: 'GOOGLE_CLIENT_SECRET',
hostedDomain: 'acme.com',
},
],hostedDomainlimits sign-in to one Workspace domain. The server asks Google to show only that domain's accounts, then checks thehdclaim of the token. Other accounts see "Sign in with your acme.com account."- Issuer:
https://accounts.google.com. - Google ID tokens carry no groups. Use
emailsanddomainsin your access groups, or add groups with personalization.
Without hostedDomain, any Google account can sign in
Anyone with a Gmail address passes Google sign-in. Set hostedDomain, or restrict access with groups and mode: 'private'.
Microsoft Entra ID
Microsoft Entra ID (formerly Azure AD) work and school accounts.
Register the app
- In the Microsoft Entra admin center, go to Identity → Applications → App registrations → New registration.
- Name it, choose Accounts in this organizational directory only.
- Under Redirect URI, pick Web and enter
https://docs.acme.com/_auth/callback/microsoft. - On the Overview page, copy the Application (client) ID and the Directory (tenant) ID.
- Under Certificates & secrets → Client secrets, create a secret and copy its Value (not its ID). It is shown once.
Configure
providers: [
{
type: 'microsoft',
tenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47',
clientId: 'b3c1a2d4-0000-4000-8000-123456789abc',
clientSecretEnv: 'ENTRA_CLIENT_SECRET',
},
],tenantIdis required. Use your tenant's ID, notcommonororganizations: the issuer must be a single tenant (https://login.microsoftonline.com/<tenantId>/v2.0).- Email: Entra often omits the
emailclaim, so the server falls back topreferred_username(the user principal name, usually an email address). - Groups: under Token configuration → Add groups claim, choose Security groups. Entra then puts group object IDs (GUIDs) in
groups, so list those IDs inidpGroups. - App roles instead of groups: define roles under App roles, assign users to them in Enterprise applications, and set
groupsClaim: 'roles'.idpGroupsthen lists role values.
Large groups lists
When a user belongs to more than 200 groups, Entra leaves groups out of the token. Use app roles or filter the groups claim to the groups assigned to the app.
Okta
Create the app integration
- In the Okta Admin Console, go to Applications → Applications → Create App Integration.
- Choose OIDC - OpenID Connect and Web Application.
- Under Sign-in redirect URIs, add
https://docs.acme.com/_auth/callback/okta. - Under Assignments, choose who can use the app.
- Copy the Client ID and Client secret from the General tab.
Configure
providers: [
{
type: 'okta',
domain: 'acme.okta.com',
clientId: '0oa1b2c3d4e5f6g7h8i9',
clientSecretEnv: 'OKTA_CLIENT_SECRET',
scopes: ['openid', 'email', 'profile', 'groups'],
},
],domainis your Okta domain, with or withouthttps://.authorizationServer: leave it out to use the org authorization server (issuerhttps://acme.okta.com). Set it to a custom server id, such asdefault, to use that one (issuerhttps://acme.okta.com/oauth2/default).- Groups with the org authorization server: on the app's Sign On tab, edit OpenID Connect ID Token, set the Groups claim type to Filter, name it
groups, and pick a filter (for example Matches regex.*). Request thegroupsscope as shown above. - Groups with a custom authorization server: under Security → API → Authorization Servers → (server) → Claims, add a
groupsclaim of type Groups, included in the ID token.
Auth0
Create the application
- In the Auth0 Dashboard, go to Applications → Applications → Create Application.
- Choose Regular Web Applications.
- On the Settings tab, add
https://docs.acme.com/_auth/callback/auth0to Allowed Callback URLs, and save. - Copy the Domain, Client ID and Client Secret.
Configure
providers: [
{
type: 'auth0',
domain: 'acme.us.auth0.com',
clientId: 'Xyz123AbC456dEf789',
clientSecretEnv: 'AUTH0_CLIENT_SECRET',
groupsClaim: 'https://docs.acme.com/groups',
},
],domain: your Auth0 domain, or your custom domain if you use one. The issuer ishttps://<domain>/.- Auth0 adds no groups claim by default. Add one with a post-login Action, then set
groupsClaimto the same claim name:
exports.onExecutePostLogin = async (event, api) => {
// Roles assigned to the user in Auth0 become docs groups.
api.idToken.setCustomClaim('https://docs.acme.com/groups', event.authorization?.roles ?? []);
};Clerk
Use this preset for staff who sign in to a Clerk instance through Clerk's OAuth applications. To reuse the Clerk session your customers already have in your product, use an app session instead.
Create the OAuth application
- In the Clerk Dashboard, go to Configure → OAuth applications and add one.
- Add
https://docs.acme.com/_auth/callback/clerkas a redirect URI. - Make sure the
openid,emailandprofilescopes are enabled. - Copy the client ID and client secret.
Configure
providers: [
{
type: 'clerk',
domain: 'clerk.acme.com',
clientId: 'abc123XYZ',
clientSecretEnv: 'CLERK_OAUTH_SECRET',
},
],domainis your Clerk Frontend API domain, such asclerk.acme.comorexample-name.clerk.accounts.dev. The issuer ishttps://<domain>.- Clerk ID tokens carry no groups. Use
emailsanddomains, or personalization.
Keycloak
Create the client
- In the Keycloak admin console, pick your realm and go to Clients → Create client.
- Client type OpenID Connect, client ID such as
docs. - Turn Client authentication on (a confidential client) and keep Standard flow checked.
- Under Valid redirect URIs, add
https://docs.acme.com/_auth/callback/keycloak. - On the Credentials tab, copy the Client secret.
Configure
providers: [
{
type: 'keycloak',
name: 'Acme SSO',
url: 'https://auth.acme.com',
realm: 'acme',
clientId: 'docs',
clientSecretEnv: 'KEYCLOAK_CLIENT_SECRET',
},
],urlis the Keycloak base URL; the issuer is<url>/realms/<realm>. Older Keycloak versions served under/authneed it in the URL:https://auth.acme.com/auth.- Groups: open the client's Client scopes tab, then its dedicated scope, and Add mapper → By configuration → Group Membership. Set Token Claim Name to
groupsand turn Full group path off, so groups arrive asstaffrather than/staff. - For local testing over
http://, the server allows anhttpissuer.
Combine providers
You can list several providers, and add an appSession too. Each gets its own button. A common setup: staff use company SSO, customers use their product account.
access: {
groups: {
staff: { idpGroups: ['docs-staff'] },
partners: { idpGroups: ['partner'] },
},
providers: [
{ type: 'okta', domain: 'acme.okta.com', clientId: '0oa1b2c3', clientSecretEnv: 'OKTA_CLIENT_SECRET', scopes: ['openid', 'email', 'profile', 'groups'] },
],
appSession: { type: 'supabase', projectUrl: 'https://abcd1234.supabase.co', loginUrl: 'https://app.acme.com/login' },
},Troubleshooting
| Symptom | Cause |
|---|---|
| The provider shows a redirect URI mismatch error | The callback URL registered with the provider doesn't match exactly. Check scheme, host, base path and id. Behind a proxy, set publicUrl (see Private docs). |
<VAR> is not set (client secret for <name>) | The clientSecretEnv variable is missing on the server. |
Your sign-in expired. Please try again. | More than 10 minutes passed, or the od_flow cookie was lost (for example, a different host or base path on the way back). |
Sign-in failed: … | Discovery or the token exchange failed. The message says why: a wrong domain, tenantId or realm, a wrong secret, or no network access to the provider. |
Your account has no email address. | Neither the token nor userinfo had an email. Request the email scope. |
<email> doesn't have access to these docs. | private mode with groups, and the reader is in none of them. |
Next steps
Private docs
Decide who can read which pages and APIs, and let readers sign in with company SSO or the account they already have in your product.
Your product's session
Let readers who are signed in to your product read private docs with no second sign-in, using their Supabase, Clerk, Firebase or Appwrite session.

