Skip to content

Troubleshooting SSO

Start from the error code, then match the symptom. Denial codes appear in the redirect as ?error=<code>. The full list is in Tenants, organizations and roles. Use the chart to pick a path, then the table for the fix.

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#E0E7FF","primaryBorderColor":"#6366F1","primaryTextColor":"#1E1B4B","secondaryColor":"#D1FAE5","secondaryBorderColor":"#10B981","secondaryTextColor":"#064E3B","tertiaryColor":"#FEF3C7","tertiaryBorderColor":"#F59E0B","tertiaryTextColor":"#6B7A90","lineColor":"#7A889C","clusterBkg":"rgba(99,102,241,0.07)","clusterBorder":"#A5B4FC","noteBkgColor":"#FEF9C3","noteTextColor":"#422006","textColor":"#6B7A90","titleColor":"#6B7A90","signalColor":"#7A889C","signalTextColor":"#6B7A90","loopTextColor":"#6B7A90","edgeLabelBackground":"#F1F5F9","actorLineColor":"#94A3B8"}}}%%
%% eq-theme:v1
flowchart TD
  A["Login failed"] --> B{"Did the browser reach the web app?"}
  B -- "No" --> C["Check OIDC_ENABLED, provider slug and redirect host"]
  B -- "Yes" --> D{"Is there an error code?"}
  D -- "No" --> E["Check the handoff: code expired, reused or lost"]
  D -- "Yes" --> F{"Which group?"}
  F -- "org_*" --> G["Fix tenant slug or organization membership"]
  F -- "hd or tid" --> H["Fix the domain or directory allow-list"]
  F -- "account_exists_unlinked or jit_disabled" --> I["Fix account linking or JIT policy"]
  F -- "Refresh errors" --> J["Sign in again after checking tenant and membership"]
%% eq-classes
classDef eqBad fill:#FEE2E2,stroke:#EF4444,color:#7F1D1D
classDef eqActor fill:#FCE7F3,stroke:#EC4899,color:#500724
classDef eqLlm fill:#FEF3C7,stroke:#F59E0B,color:#451A03
class A,D eqBad
class B eqActor
class C eqLlm

Read it from the top: the first question is whether the browser reached the web app. Without a redirect, the problem is the setup. With a code, it is a policy decision.

Symptom Cause Fix
GET /api/v1/auth/oidc/login returns 503 OIDC is off, or the provider slug is unknown Set EDGEQUAKE_OIDC_ENABLED=true and EDGEQUAKE_AUTH_ENABLED=true, then check the slug in GET /api/v1/auth/sso/providers
?error=org_unknown The tenant for the organization alias does not exist Create a tenant whose slug equals the organization alias
?error=org_missing The token has no organization claim (the user is not a member, or the scope is missing) Add the user to the organization, and keep the organization scope requested
?error=org_ambiguous:a,b Several organizations and no hint Pick one in the UI picker, or pass ?org=
?error=hd_mismatch The Google domain is not in EDGEQUAKE_OIDC_ALLOWED_HD Add the domain, or use an account from an allowed domain
?error=idp_tenant_not_allowed The Entra directory is not in EDGEQUAKE_OIDC_ALLOWED_TID Add the directory GUID, or use an account from an allowed directory
?error=account_exists_unlinked The same email is already a local account Sign in with the password, or enable verified-email linking deliberately
?error=jit_disabled EDGEQUAKE_OIDC_JIT=false Create the user and membership in advance, or enable JIT
The web UI shows invalid_handoff (the API returns 401, reason code_invalid) The one-time code expired (90 s), was already used, or the tab was restored Start sign-in again
Login loops, or state_expired The login host differs from the redirect URI host, so the state cookie is lost Use the same origin for login start and REDIRECT_URI
Issuer mismatch at startup or callback The issuer string differs from the discovery issuer Set Keycloak KC_HOSTNAME, and use an identical string
Keycloak form shows again after a correct password Keycloak brute-force protection locked the user after quick failures Wait, or unlock the user in the Keycloak Admin console
Realm edits are ignored The import skips realms that already exist Edit in the Admin console, or drop the database volume (dev only)
Refresh returns 401 session_revoked Back-channel logout, or an admin revoked the session Sign in again
Refresh returns 403 tenant_suspended or membership_revoked The tenant or membership changed Restore access, then sign in again
Server exits at boot with a pending migration (exit code 78) The schema gate refuses to serve Run edgequake migrate, or set EDGEQUAKE_SCHEMA_GATE=wait

To check the Keycloak side on its own, run python3 scripts/keycloak_smoke.py. Add --api URL to include the EdgeQuake handoff.