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.
In the Okta admin console: Applications → Create App Integration → OIDC → Web Application.
https://YOUR-HOST/authorize (must match okta.redirect_uri
exactly, including scheme and trailing path).https://YOUR-HOST/login (optional).Note the Client ID, Client secret, and your issuer (usually
https://TENANT.okta.com/oauth2/default — verify at {issuer}/.well-known/openid-configuration).
[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.
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.
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.[okta] key or auth.mode requires a restart (the OIDC client is
registered at boot).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.