# SSO / OIDC

Source: https://docs.externa.qiick.io/docs/sso-oidc

Externa supports optional **OIDC/SSO** alongside Fortify password and passkey login. Configure a single standards-compliant identity provider (Keycloak, Azure AD OIDC, Google Workspace OIDC, …) in Project settings.

> **Related**
> Project settings form: [Project settings](/docs/project-settings). Local auth stack: [Passkeys](/docs/passkeys) · [Account settings](/docs/account-settings). Env / `APP_URL`: [Environment variables](/docs/environment-variables). Pre-ship gates: [Security checklist](/docs/security-checklist).

## What ships in v1

| Concern    | Behavior                                                                                                 |
| ---------- | -------------------------------------------------------------------------------------------------------- |
| Protocol   | OpenID Connect authorization code via issuer discovery (`/.well-known/openid-configuration`)             |
| Package    | `laravel/socialite` + in-app generic OIDC driver                                                         |
| Login UX   | Optional secondary button on `/login` when OIDC is fully configured                                      |
| Linking    | Match IdP `sub` first; else link by **verified** email to an existing active user and store `oidc_sub`   |
| JIT        | Off by default. When on, create users from verified email; honor `allowed_domains` + `default_user_role` |
| Secrets    | Client secret stored encrypted; edit payloads expose `oidc_client_secret_configured` only                |
| Local auth | Password + passkey paths stay available                                                                  |

## Out of scope (v1)

SAML, SCIM, multi-IdP, claim→role matrices, IdP-initiated SSO, treating OIDC as an MFA substitute, forcing SSO-only (disabling password/passkey).

## Setup

1. In the IdP, create a confidential OIDC client.
2. Set redirect URI to `{APP_URL}/auth/oidc/callback` (same class of footgun as passkeys / `APP_URL`).
3. In Externa **Settings → Project → SSO / OIDC**:
   - Enable OIDC
   - Issuer URL (base; discovery is appended)
   - Client ID + client secret
   - Optional button label
   - Optional JIT provisioning
4. Ensure target users already exist (JIT off) **or** turn JIT on and configure allowed domains / default role.

| Route                     | Name                 | Notes                                         |
| ------------------------- | -------------------- | --------------------------------------------- |
| `GET /auth/oidc/redirect` | `auth.oidc.redirect` | Starts authorize redirect (404 when disabled) |
| `GET /auth/oidc/callback` | `auth.oidc.callback` | Token + userinfo → session login              |

If the callback fails validation or the IdP rejects the login, you are redirected to `/login` with the error shown — no redirect loop back to the IdP.

For local QA, a Keycloak profile ships in `compose.oidc.yaml` (not for production):

```bash
docker compose -f compose.yaml -f compose.oidc.yaml --profile oidc up --build
```

## Security notes

- **Never link on unverified email** — account takeover risk. Externa requires IdP `email_verified`.
- After first successful link, prefer `oidc_sub` over email.
- Inactive / soft-deleted users are rejected.
- OIDC login does **not** satisfy `two_factor_required`. SSO-only cohorts still need TOTP/passkey enrollment or the project MFA gate off.
- Do not trust arbitrary `roles` / `groups` claims without an allowlist (not implemented in v1).

## Operator checklist

- [ ] Redirect URI matches production `APP_URL`
- [ ] Client secret saved (configured flag true); plaintext never returned on edit
- [ ] Link-only path verified with an existing user
- [ ] If JIT on: allowed domains + default role verified; unknown domains rejected
- [ ] Password and passkey login still work
