Skip to main content

OIDC setup

Orbit has no local passwords — sign-in is always via an external OIDC provider using the Authorization Code flow with PKCE. OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET and OIDC_REDIRECT_URI are always required (see Configuration).

Any provider that implements OIDC Discovery works — the sections below are worked examples for the common ones, not a list of what is supported.

The redirect URI rule​

The redirect URI is always <external-url>/auth/callback, where <external-url> is the URL a browser reaches Orbit at:

  • http://localhost:3000/auth/callback in local development
  • https://storage.example.com/auth/callback in production

Whatever you register with your provider must match OIDC_REDIRECT_URI exactly — scheme, host, port and path. Orbit sends the configured value at both legs of the flow (authorization and token exchange), so a mismatch fails the exchange rather than silently logging someone in.

Behind a reverse proxy or ingress

Set OIDC_REDIRECT_URI to the public HTTPS URL, even though the app itself receives plain HTTP behind the proxy. Orbit deliberately uses the configured value rather than reconstructing the URL from the request, so it does not depend on X-Forwarded-Proto being set. The session cookie's Secure flag is derived from this value too, which is another reason it must be the https:// one.

What Orbit reads from the token​

ClaimUsed forRequired
subThe user's identity — the key their profiles and settings hang offYes. Login fails without it
emailDisplay in the nav railNo
nameDisplay in the nav railNo

Providers disagree about where they put the last two, so Orbit falls back in this order:

  • email: email → preferred_username → upn
  • name: name → given_name + family_name → preferred_username → email

If the ID token carries neither an email nor a name, Orbit makes one call to the provider's userinfo endpoint and fills the gaps from there. Providers that put everything in the ID token never incur that request.

warning

preferred_username and upn are not verified email addresses — Entra ID returns a UPN that merely looks like one. Orbit uses them for display only, and identity is always sub. Do not treat the email Orbit shows as proof of an address in another system.

Orbit has no authorization model: any user who completes the flow gets full access, and there is no admin or read-only tier. Restrict who may sign in at the provider — see Security.

Keycloak​

  1. In your realm, Clients → Create client. Client type OpenID Connect, client ID of your choosing.
  2. Client authentication: On (confidential). Leave Standard flow enabled; the others are not needed.
  3. Valid redirect URIs: your <external-url>/auth/callback.
  4. Copy the secret from the Credentials tab.
OIDC_ISSUER_URL=https://id.example.com/realms/my-realm
OIDC_CLIENT_ID=orbit-object-console
OIDC_CLIENT_SECRET=<from the Credentials tab>
OIDC_REDIRECT_URI=https://storage.example.com/auth/callback

The issuer is the realm URL — no /protocol/openid-connect suffix. Keycloak puts email and name in the ID token, so no userinfo call is made.

To restrict access, add a group or role condition to the client rather than leaving it open to the whole realm.

Microsoft Entra ID (Azure AD)​

  1. App registrations → New registration. Under Redirect URI choose Web and enter your <external-url>/auth/callback.
  2. Certificates & secrets → New client secret; copy the Value (not the Secret ID) — it is shown once.
  3. API permissions: the delegated openid, profile and email permissions under Microsoft Graph are enough. No admin consent is needed for these.
OIDC_ISSUER_URL=https://login.microsoftonline.com/<tenant-id>/v2.0
OIDC_CLIENT_ID=<application (client) ID>
OIDC_CLIENT_SECRET=<the secret Value>
OIDC_REDIRECT_URI=https://storage.example.com/auth/callback

Three things that trip people up:

  • The /v2.0 suffix is required. Without it discovery returns the v1 endpoints, whose ID tokens differ. Use the tenant GUID rather than common or organizations unless you genuinely intend a multi-tenant app — those issuers vary per tenant at runtime and will not match a fixed issuer.
  • email is often absent from the ID token. Entra ID only emits it when the account has a mail attribute or the optional claim is configured. Orbit handles this: it falls back to preferred_username and, failing that, calls userinfo. You do not need to configure the optional claim, though you may if you want a real mail address rather than a UPN.
  • Restrict access in the app registration, under Enterprise applications → Properties → Assignment required, plus user/group assignment. Otherwise every account in the tenant can sign in.

Google​

  1. In Google Cloud Console: APIs & Services → Credentials → Create credentials → OAuth client ID, application type Web application.
  2. Authorized redirect URIs: your <external-url>/auth/callback.
  3. Configure the OAuth consent screen. For an organisation, set the user type to Internal so only your Workspace accounts can sign in.
OIDC_ISSUER_URL=https://accounts.google.com
OIDC_CLIENT_ID=<client id>.apps.googleusercontent.com
OIDC_CLIENT_SECRET=<client secret>
OIDC_REDIRECT_URI=https://storage.example.com/auth/callback

The issuer has no path component. Google returns email and name in the ID token, so no userinfo call is made. Google does not offer per-application access rules beyond the consent screen's user type — if you need finer control, put Orbit behind a provider that does.

Any other provider​

Authentik, Auth0, Okta, Zitadel, Dex, GitLab and anything else implementing OIDC Discovery work the same way. You need four things:

  1. The issuer URL — the value where <issuer>/.well-known/openid-configuration resolves. Check it before anything else:

    curl -s https://id.example.com/realms/my-realm/.well-known/openid-configuration | head

    If that does not return JSON, the issuer URL is wrong and Orbit will fail to start with a discovery error.

  2. A confidential client with the Authorization Code flow enabled. Orbit sends PKCE (S256) as well; providers that require PKCE are fine, and those that ignore it are too.

  3. The redirect URI registered exactly as above.

  4. Scopes, if your provider needs something other than the default openid profile email — set OIDC_SCOPES (space-separated). openid must always be included.

OIDC_ISSUER_URL=https://id.example.com
OIDC_CLIENT_ID=orbit-object-console
OIDC_CLIENT_SECRET=<client secret>
OIDC_REDIRECT_URI=https://storage.example.com/auth/callback
OIDC_SCOPES="openid profile email" # only if the default is not right

Troubleshooting​

SymptomCause
Server exits at startup with a discovery errorOIDC_ISSUER_URL is wrong, unreachable, or (for Entra ID) missing /v2.0. Test the .well-known URL with curl
invalid_grant / "Incorrect redirect_uri" after signing inThe registered redirect URI does not match OIDC_REDIRECT_URI byte for byte — most often http vs https behind a proxy
invalid_client at the token stepWrong client secret, or the client is public rather than confidential
Login succeeds but the nav rail shows no emailThe provider supplied neither email nor preferred_username nor a userinfo endpoint that returns them. Harmless — identity is sub
Login fails with "no sub claim"The provider returned a token without sub. Check that you are using an OIDC client, not a plain OAuth2 one
Everyone in the directory can sign inExpected — Orbit has no authorization model. Restrict at the provider

The server logs the reason for a failed exchange, so kubectl logs (or the container's stdout) is the fastest place to look.