SSO¶
Lets your team sign into the admin dashboard through your own identity provider (Okta, Microsoft Entra ID, Google Workspace, Auth0, or any standards-compliant OIDC provider) instead of an AiFlow-specific email and password. Email and password login (see Team) keeps working alongside SSO: this is an addition, not a replacement.
How it works¶
sequenceDiagram
participant Browser
participant AiFlow as AiFlow backend
participant IdP as Your identity provider
Browser->>AiFlow: GET /api/v1/auth/oidc/login
AiFlow-->>Browser: 307 redirect + state cookie
Browser->>IdP: authorize (state, redirect_uri)
IdP-->>Browser: admin approves
Browser->>AiFlow: GET /api/v1/auth/oidc/callback?code&state
AiFlow->>IdP: exchange code for tokens
IdP-->>AiFlow: id_token
AiFlow->>AiFlow: verify signature against the IdP's own JWKS
AiFlow-->>Browser: 307 redirect to the admin SPA, token in the URL fragment
The state round trip provides CSRF protection: a short-lived, HttpOnly
cookie set when /oidc/login is called must match the state query
parameter that /oidc/callback receives back, or the attempt is
rejected. The ID token's signature is verified against the identity
provider's own published JWKS before anything in it, particularly the
email claim, is trusted.
Activating it¶
SSO needs two independent things: the licence has to grant sso, and you
have to turn it on. The licence gate came first historically but was not
enforced until recently, so a deployment that had SSO working purely from
the setting will find it switched off after upgrading unless its licence
carries the right. GET /api/v1/auth/sso-config reports enabled: false
when either half is missing, and the login page simply hides the button
rather than offering one that fails.
Then set these environment variables on the backend (see .env.example):
| Variable | Required | Description |
|---|---|---|
OIDC_ENABLED |
Yes | true to turn SSO on. The login page shows an SSO button only when this is set. |
OIDC_ISSUER_URL |
Yes | Your identity provider's issuer URL, e.g. https://your-tenant.okta.com. AiFlow discovers everything else (authorize/token endpoints, JWKS) from {issuer}/.well-known/openid-configuration. |
OIDC_CLIENT_ID |
Yes | This deployment's OAuth2 client id, from registering AiFlow as an application with your identity provider. |
OIDC_CLIENT_SECRET |
Yes | The matching client secret. |
ADMIN_DASHBOARD_URL |
Yes | The admin SPA's own public origin, e.g. https://admin.your-domain.com. The backend and the admin SPA are separate deployments; this is how the callback knows where to hand the finished login back to. |
None of these five are validated at startup. Whether the Sign in with
SSO button appears is decided by OIDC_ENABLED and the sso right
together, and by nothing else here. In particular,
ADMIN_DASHBOARD_URL has no default. Leaving it unset doesn't stop the
button from showing up: it breaks the last step of a sign-in attempt
instead. /oidc/login and the redirect to your identity provider both
succeed regardless, and the identity provider hands control back to
/oidc/callback successfully too. Only then does AiFlow discover it has
nowhere to send the browser, and it returns a 500
("ADMIN_DASHBOARD_URL is not configured; cannot complete the SSO
handoff") instead of the final redirect. Set all five together before
testing, not just the four OIDC_* ones.
Registering AiFlow as an application with your identity provider works
against any standards-compliant OIDC provider. There's no AiFlow-specific
code per provider, just a discovery document every provider serves the
same way, at {issuer}/.well-known/openid-configuration. Every provider
needs the same two things:
- Redirect URI (also called the callback URI):
{PUBLIC_BASE_URL}/api/v1/auth/oidc/callback(the backend's own URL, not the admin SPA's). - Scopes:
openid email profile. AiFlow only reads theemailandemail_verifiedclaims from the ID token. Nothing else is requested.
The concrete steps below cover the four most common providers. If yours isn't listed, look for "register an OIDC or OpenID Connect application" in its docs: every provider asks for the same handful of things.
- Okta admin console → Applications → Applications → Create App Integration.
- Sign-in method: OIDC - OpenID Connect. Application type: Web Application.
- Set Sign-in redirect URIs to the callback URI above.
- Under Assignments, control which Okta users or groups can sign into AiFlow.
- Save. The app's General tab shows Client ID and Client secret.
OIDC_ISSUER_URLis your Okta domain plus the authorization server you assigned this app to, commonlyhttps://your-tenant.okta.com/oauth2/default(the Issuer URI field under Security → API → Authorization Servers shows the exact value for whichever server you used).
- Auth0 dashboard → Applications → Create Application → Regular Web Applications.
- In Settings, add the callback URI above to Allowed Callback URLs.
OIDC_ISSUER_URLishttps://your-tenant.us.auth0.com/(your tenant's Domain field, with a trailing slash, exactly as shown on the Settings page: Auth0's discovery document 404s without it).- Client ID and Client Secret are on the same Settings page.
- Entra admin center → Applications → App registrations → New registration.
- Redirect URI: platform Web, value the callback URI above.
- After creation, Certificates & secrets → New client secret, copy its Value immediately (Entra shows it once).
OIDC_CLIENT_IDis the app's Application (client) ID, shown on the Overview page.OIDC_ISSUER_URLmust be the v2.0 endpoint, not the older v1 one:https://login.microsoftonline.com/{tenant-id}/v2.0, where{tenant-id}is also on the Overview page (Directory (tenant) ID). AiFlow's OIDC discovery fails against the v1 issuer.- Under API permissions, confirm
email,openid, andprofileare present (Entra addsopenidandprofileby default, so addemailexplicitly if it isn't already there).
- Google Cloud Console → select or create a project → APIs & Services → Credentials → Create Credentials → OAuth client ID.
- If prompted, configure the OAuth consent screen first: User type Internal (restricts sign-in to your Workspace domain, recommended) or External, then fill in the required app name and support email.
- Application type: Web application. Add the callback URI above under Authorized redirect URIs.
- Save. The Client ID and Client secret show immediately.
OIDC_ISSUER_URLis alwayshttps://accounts.google.comfor every Google Workspace or Google account: there's no per-tenant value.
Signing in¶
The login page shows a Sign in with SSO button whenever
GET /api/v1/auth/sso-config reports {"enabled": true}. This endpoint
is public and needs no admin token, since the login page hasn't
authenticated anyone yet. The response's provider_label field names
the button after the provider when AiFlow recognizes OIDC_ISSUER_URL's
host (Google Workspace, Okta, Auth0, Microsoft Entra ID), for example
"Sign in with Google". It's null for any other standards-compliant
provider, and the button falls back to the generic label. Clicking it is
a normal top-level browser navigation to GET /api/v1/auth/oidc/login,
which redirects into your identity provider. There's nothing to call
from a script: this isn't a fetch-and-parse API.
First-time sign-in: auto-provisioning¶
If the ID token's email doesn't match any existing admin, AiFlow creates one automatically at the Viewer role, the least-privileged role. Per per-agent access, the new account starts with zero granted agents. An Owner or Admin then raises the role and grants agent access the same way as for any other admin: see Change an admin's role and Set an admin's granted agents.
If the email does match an existing admin, that account's existing role and agent grants apply unchanged. SSO is just a different way to authenticate as that account, not a separate identity.
A deactivated account (see Deactivate an admin) can't sign in via SSO either, same as with a password.
Errors¶
A failed attempt redirects back to the admin SPA's login page with a
?sso_error=<reason> query parameter:
| Reason | Meaning |
|---|---|
state_mismatch |
The state cookie was missing or didn't match, the link expired or was reused. |
exchange_failed |
The identity provider rejected the code exchange, or its ID token didn't verify. |
email_not_verified |
The ID token's email_verified claim was explicitly false. |
account_inactive |
The matching admin account has been deactivated. |
Next¶
- Team: roles, per-agent access, and the rest of admin-user management that SSO-provisioned accounts go through like any other.