SSO providers
Getting a client ID and secret from each provider, and wiring it into the engine.
Every provider follows the same shape: register an application on the
provider's developer console, whitelist a redirect URI that points back at the
engine, then put the resulting client ID and secret in .env.
Configure only the providers you want — the rest are skipped at startup. For how an SSO session behaves once a user is signed in, see Session policy.
The redirect URI
Every provider needs a redirect URI whitelisted, and it must match what the
engine sends byte for byte — a trailing slash or an http / https
mismatch is enough for the provider to reject the request.
The engine derives it from API_BASE_URL:
{API_BASE_URL}/auth/{provider}/callbackSo with API_BASE_URL=http://localhost:5201:
| Provider | Redirect URI |
|---|---|
http://localhost:5201/auth/google/callback | |
| GitHub | http://localhost:5201/auth/github/callback |
| Microsoft | http://localhost:5201/auth/microsoft/callback |
http://localhost:5201/auth/linkedin/callback | |
| Apple | http://localhost:5201/auth/apple/callback |
Change API_BASE_URL for production and re-register the production URI
alongside the local one — most providers accept several.
Providers you have not configured are simply skipped. The engine logs which ones it enabled at startup and answers the routes of the rest with a 404, so you can add providers one at a time.
Create a project
Open the Google Cloud Console and select or create a project using the project picker in the top bar.

Configure the consent screen
Go to APIs & Services → OAuth consent screen. Choose External unless this is a Google Workspace-only deployment, then fill in the app name, support email and developer contact.
While the app is in Testing, only accounts listed under Test users can sign in — add your own address there or you will be refused at the consent step.

Create the credentials
Go to APIs & Services → Credentials → Create credentials → OAuth client ID and pick Web application.
Under Authorised redirect URIs, add:
http://localhost:5201/auth/google/callback
Copy the values
Google shows the client ID and client secret once the client is created. The secret can be revealed again later from the same screen.
GOOGLE_CLIENT_ID=000000000000-xxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxx
Scopes requested: email, profile.
GitHub
Register an OAuth app
Go to Settings → Developer settings → OAuth Apps → New OAuth App, or open github.com/settings/developers directly.

Fill in the form
Two fields are easy to confuse:
- Homepage URL — your frontend, e.g.
http://localhost:5200. Shown on the consent screen; not used in the flow. - Authorization callback URL — the engine callback:
http://localhost:5201/auth/github/callback
Leave Enable Device Flow unchecked; the engine uses the standard web flow.

Generate a secret
GitHub shows the client ID immediately. Click Generate a new client secret for the secret — it is displayed once, so copy it before leaving the page.
GITHUB_CLIENT_ID=Ov23lixxxxxxxxxxxxxx
GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Scope requested: user:email.
GitHub only releases an email address if the account has one that is verified. A user whose email is private will still authenticate, but the engine rejects the login because it has no address to match against a tenant user.
Microsoft (Entra ID)
Register an application
Open the Azure portal, go to Microsoft Entra ID → App registrations → New registration, and give the app a name.

Choose the account types
Supported account types decides who can sign in:
| Choice | Who can sign in |
|---|---|
| Single tenant | Only your own organisation |
| Multitenant | Any organisational directory |
| Multitenant + personal | Organisations and personal Microsoft accounts |
The engine authenticates against the common endpoint, which suits the
multitenant options. Pick single tenant only if you intend to restrict sign-in
to one directory.
Create a client secret
Go to Certificates & secrets → Client secrets → New client secret. Copy the Value, not the Secret ID — the value is only shown immediately after creation.
Note the expiry you choose: Entra secrets expire (24 months maximum), and sign-in breaks when they do.
MICROSOFT_CLIENT_ID=00000000-0000-0000-0000-000000000000
MICROSOFT_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Scope requested: user.read. The application ID (client) ID is on the app's
Overview page.
LinkedIn is wired through OpenID Connect, not the older LinkedIn OAuth
endpoints — the r_liteprofile and r_emailaddress scopes that older
integrations used have been retired and cannot be requested by new apps.
Create an app
Go to the LinkedIn developer portal and create an app. LinkedIn requires it to be associated with a Company Page, and you must verify that association before products can be requested.

Request the right product
On the Products tab, add Sign In with LinkedIn using OpenID Connect.
Without this product the openid scope is refused.

Add the redirect URL
On the Auth tab, under Authorized redirect URLs for your app, add:
http://localhost:5201/auth/linkedin/callback
Copy the credentials
The Auth tab holds the client ID and client secret.
LINKEDIN_CLIENT_ID=xxxxxxxxxxxxxx
LINKEDIN_CLIENT_SECRET=xxxxxxxxxxxxxxxx
Scopes requested: openid, profile, email.
Apple
Sign in with Apple is the most involved of the five, and the only one that
cannot be tested against localhost.
Apple requires a paid Apple Developer Program membership, rejects
http:// and localhost redirect URIs outright, and posts its callback as a
POST with a form-encoded body. To test locally you need an HTTPS tunnel
(ngrok or similar) and API_BASE_URL set to that public HTTPS origin.
Create an App ID
In the Apple Developer portal, go to Certificates, Identifiers & Profiles → Identifiers, create an App IDs identifier, and enable the Sign in with Apple capability.

Create a Services ID
Create a second identifier of type Services IDs. This one — not the App ID
— is your APPLE_CLIENT_ID.
Enable Sign in with Apple, then Configure it to add your domain and the return URL:
https://your-public-host/auth/apple/callback
Create a signing key
Under Keys, create a key with Sign in with Apple enabled. Download the
.p8 file — Apple allows this once only. The key ID is shown on the key's
page, and the team ID is in the top-right of the developer portal.

Set the variables
APPLE_PRIVATE_KEY is the contents of the .p8 file. Keep the newlines — if
your process manager cannot hold a multi-line value, replace real newlines with
\n and quote the string.
APPLE_CLIENT_ID=com.yourcompany.yourapp.service
APPLE_TEAM_ID=XXXXXXXXXX
APPLE_KEY_ID=XXXXXXXXXX
APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"Scopes requested: email, name. Apple only returns the user's name on the
first authorisation; later sign-ins carry the identifier alone.
Verifying a provider
With the engine running, request the initiation route and inspect the redirect it returns:
curl -s -o /dev/null -D - "http://localhost:5201/auth/github"A correctly configured provider answers 302 with a Location pointing at
the provider's authorisation endpoint. Following that URL is a useful second
check: providers validate the client ID and redirect URI before showing a
login screen, so reaching a login page means both are registered correctly.
A misconfigured provider fails in one of a few recognisable ways:
| Symptom | Cause |
|---|---|
Provider returns 404 … is not enabled | Its variables are unset — check the startup log for which are missing |
redirect_uri_mismatch on the provider's page | Registered URI differs from {API_BASE_URL}/auth/{provider}/callback |
| Redirected back to login with No active user… | Provider authenticated, but no active tenant user has that email |
| Redirected back to login with Organization ID is required | The sign-in was started without an account ID |
The client secret is only exercised when the provider redirects back and the engine exchanges the code for a token. A successful redirect to the provider proves the client ID and redirect URI are right; only a completed sign-in proves the secret is.
