# Okta OIDC setup The app authenticates via Okta using OpenID Connect (Authlib). Okta settings live only in `config.toml` `[okta]` — they are never editable from the admin panel. ## 1. Create the Okta app In the Okta admin console: **Applications → Create App Integration → OIDC → Web Application**. - **Sign-in redirect URI:** `https://YOUR-HOST/authorize` (must match `okta.redirect_uri` exactly, including scheme and trailing path). - **Sign-out redirect URI:** `https://YOUR-HOST/login` (optional). - **Grant type:** Authorization Code. - **Assignments:** grant the app to the users/groups who should be able to *reach* the login (fine-grained allow/deny is still enforced by this app's own users list). Note the **Client ID**, **Client secret**, and your **issuer** (usually `https://TENANT.okta.com/oauth2/default` — verify at `{issuer}/.well-known/openid-configuration`). ## 2. Configure the app ```toml [auth] mode = "oidc" denied_message = "Your account is not authorised. Contact IT." [[auth.users]] email = "you@company.com" role = "admin" [okta] issuer = "https://TENANT.okta.com/oauth2/default" client_id = "0oaXXXXXXXX" client_secret = "XXXXXXXX" redirect_uri = "https://YOUR-HOST/authorize" ``` The app requests scopes `openid email profile` and identifies users by the **email** claim. ## 3. Access model Okta decides *who can authenticate*; this app's `[[auth.users]]` list decides *who is allowed in and with what role*. An authenticated user not on the list sees `denied_message` (HTTP 403). Roles (`admin`/`user`) come from this list and are **re-checked on every request**, so removing or demoting someone takes effect immediately without them re-logging-in. ## Gotchas - **`redirect_uri` mismatch** is the #1 failure. It must match Okta exactly. Behind a TLS-terminating proxy, do not rely on auto-detection — set the explicit `https://` value. - **`SameSite=Lax`** is required (the app sets it). `Strict` would drop the session cookie on the cross-site callback and every login would fail with `mismatching_state`. - **`cookie_secure = true`** in production (HTTPS). Only set `false` for plain-http localhost. - Changing any `[okta]` key or `auth.mode` requires a **restart** (the OIDC client is registered at boot). ## Testing without a tenant Use `[auth] mode = "static"` with `[auth.static]` credentials for local development. The app logs a loud warning and refuses to bind a non-loopback interface in this mode (override with `PPSQ_ALLOW_INSECURE_AUTH=1` for dev only). The OIDC callback logic is covered by `tests/test_oidc.py` with a monkeypatched token exchange — no tenant needed.