Engine

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}/callback

So with API_BASE_URL=http://localhost:5201:

ProviderRedirect URI
Googlehttp://localhost:5201/auth/google/callback
GitHubhttp://localhost:5201/auth/github/callback
Microsofthttp://localhost:5201/auth/microsoft/callback
LinkedInhttp://localhost:5201/auth/linkedin/callback
Applehttp://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.

Google

Create a project

Open the Google Cloud Console and select or create a project using the project picker in the top bar.

Google Cloud Console project picker

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.

Google OAuth consent screen configuration

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

Google OAuth client ID creation

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

Google client ID and secret

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.

GitHub developer settings OAuth Apps list

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.

GitHub OAuth app registration form

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

GitHub client ID and generated secret

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.

Entra ID app registration

Choose the account types

Supported account types decides who can sign in:

ChoiceWho can sign in
Single tenantOnly your own organisation
MultitenantAny organisational directory
Multitenant + personalOrganisations 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.

Add the redirect URI

Set the platform to Web and add:

http://localhost:5201/auth/microsoft/callback

Entra ID redirect URI configuration

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

Entra ID client secret value

Scope requested: user.read. The application ID (client) ID is on the app's Overview page.

LinkedIn

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.

LinkedIn app creation

Request the right product

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

LinkedIn products tab

Add the redirect URL

On the Auth tab, under Authorized redirect URLs for your app, add:

http://localhost:5201/auth/linkedin/callback

LinkedIn authorised redirect URLs

Copy the credentials

The Auth tab holds the client ID and client secret.

LINKEDIN_CLIENT_ID=xxxxxxxxxxxxxx
LINKEDIN_CLIENT_SECRET=xxxxxxxxxxxxxxxx

LinkedIn client ID and secret

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.

Apple App ID with Sign in with Apple enabled

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

Apple Services ID configuration

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.

Apple sign-in key creation

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:

SymptomCause
Provider returns 404 … is not enabledIts variables are unset — check the startup log for which are missing
redirect_uri_mismatch on the provider's pageRegistered 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 requiredThe 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.