SSO Quick Start (OIDC)
Configure OpenRag to delegate authentication to your corporate SSO (LemonLDAP::NG, Keycloak, Auth0, Azure AD, Okta…) in five steps.
New to OIDC? You just need to coordinate with your SSO admin and set six
.envvariables. No code to change.
Step 1 — Ask your SSO admin to register a client
Section titled “Step 1 — Ask your SSO admin to register a client”Give them the following information.
⚠
<openrag-host>is the OpenRag backend URL, NOT the indexer-ui front. The OIDC callback and back-channel-logout endpoints are hosted only on the backend (the container runningopenrag/api.py). If you point the IdP at the front URL instead, you get an infinite redirect loop: the front has no/auth/callbackroute, returns 404/401, which is interpreted as “not authenticated”, triggering a new OIDC flow, which again lands on the front, etc.In a typical deployment:
https://rag.mycorp.com(the backend) vshttps://ui.mycorp.com(the front). Use the backend URL for the OIDC endpoints below.
| Field | Value to give |
|---|---|
| Client type | confidential (server-to-server token exchange) |
| Grant type | authorization_code |
| Response type | code |
| Valid redirect URIs | <openrag-backend-host>/auth/callback (backend, not front!) |
| Back-channel logout URI | <openrag-backend-host>/auth/backchannel-logout (backend!) |
| Post-logout redirect URIs | an optional URL outside OpenRag (see §Step 4 below) |
| Allowed scopes | openid, email, profile, offline_access |
Include sid in tokens | ✅ enabled (required for back-channel logout) |
| Send refresh token | ✅ enabled (so the session doesn’t drop every few minutes) |
Then ask the admin for three pieces of information:
client_id— a public identifier, typicallyopenragor similar.client_secret— a long random string, shown only once by most IdPs. Store it in a password manager.- The IdP issuer URL — e.g.
https://sso.mycorp.com/orhttps://keycloak.mycorp.com/realms/mycorp.
Step 2 — Verify the exact issuer string
Section titled “Step 2 — Verify the exact issuer string”This is the most common setup mistake. The issuer value you put in .env MUST match byte-for-byte what the IdP’s discovery document advertises (including trailing slash, per OIDC Core §2). Keycloak usually has no trailing slash; LemonLDAP::NG and Auth0 usually have one.
Run this command against your IdP:
curl -s https://sso.mycorp.com/.well-known/openid-configuration | jq -r .issuerCopy the output verbatim. If you get:
https://sso.mycorp.com/→ use that with the slash.https://keycloak.mycorp.com/realms/mycorp→ use that without a slash.
Any mismatch and OpenRag refuses the login with Issuer mismatch in the logs.
Step 3 — Generate a Fernet encryption key
Section titled “Step 3 — Generate a Fernet encryption key”Access tokens and refresh tokens returned by the IdP are stored encrypted at rest. Generate a dedicated key for your deployment:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"Output example: XFlT-ZfXkdqf0v-5Z8kVt9xhU6c7Z4z0ZY8Z4Z4Z4= (44 chars, url-safe base64).
Store this in your secrets manager — losing it invalidates every stored session.
Step 4 — Populate .env
Section titled “Step 4 — Populate .env”Copy this block at the end of your .env and fill in your values:
# Switch OpenRag from the legacy Bearer-token login to OIDC. REQUIRED.AUTH_MODE=oidc
# Issuer URL — EXACT match with the curl|jq output from Step 2.OIDC_ENDPOINT=https://sso.mycorp.com/
OIDC_CLIENT_ID=openragOIDC_CLIENT_SECRET=change-me-the-secret-from-step-1
# Must match EXACTLY the "Valid redirect URI" registered in Step 1.OIDC_REDIRECT_URI=https://rag.mycorp.com/auth/callback
# From Step 3.OIDC_TOKEN_ENCRYPTION_KEY=XFlT-ZfXkdqf0v-5Z8kVt9xhU6c7Z4z0ZY8Z4Z4Z4=
# --- Optional ---# OIDC_SCOPES="openid email profile offline_access" # default# OIDC_CLAIM_SOURCE=id_token # default ; alternative: userinfo# OIDC_CLAIM_MAPPING= # default: empty (no sync of display_name/email from IdP)# OIDC_AUTO_PROVISION_LOGIN=false # default ; true = create users on first login from claims (see Step 5)
# ⚠ Where the IdP sends the user AFTER logging out.# A ("/") lands on the OpenRag root, which immediately re-triggers# OIDC login — if the IdP session is still alive you appear to be# re-logged-in instantly (no apparent "logout" effect); if it was killed# you land back on the IdP form in a loop. Prefer a URL OUTSIDE OpenRag# or nothing to let SSO doing its job:# - your corporate intranet / landing page# - a static "you are logged out" page you control# - the IdP's own post-logout URL (e.g. https://sso.mycorp.com/)# OIDC_POST_LOGOUT_REDIRECT_URI=https://intranet.mycorp.com/Tip — values with spaces (like
OIDC_SCOPES): quote them to stay safe across dotenv parsers:OIDC_SCOPES="openid email profile offline_access". Quotes are stripped on read.
Optional: sync display_name / email from the IdP
Section titled “Optional: sync display_name / email from the IdP”By default, OpenRag never modifies a user’s display_name or email after login. If you want the IdP to be the source of truth (useful when HR changes a user’s name), set:
OIDC_CLAIM_MAPPING=display_name:name,email:emailEach pair is db_field:oidc_claim. Only display_name and email are writable — is_admin, external_user_id, file_quota, and token can never be changed via the IdP.
By default OpenRag reads the claims from the verified ID token (OIDC_CLAIM_SOURCE=id_token, no extra HTTP call). Switch to userinfo if your IdP only exposes certain claims via the /userinfo endpoint.
Step 5 — Pre-provision users
Section titled “Step 5 — Pre-provision users”By default, OpenRag does not auto-create users on first login. Each user must exist in the database with their OIDC sub stored in external_user_id.
Skip this step entirely by setting
OIDC_AUTO_PROVISION_LOGIN=truein your.env. The callback then creates a non-admin user from the ID-token claims on first login and keepsdisplay_name+
Ask the IdP admin for each user’s sub claim value (stable identifier, NOT the username). Then create the user via the OpenRag admin API — you’ll need an admin AUTH_TOKEN for this:
# Boot once with AUTH_MODE=token and AUTH_TOKEN=sk-... to create users,# OR keep AUTH_TOKEN in .env alongside AUTH_MODE=oidc — in that mode the# bearer is still accepted for programmatic admin calls.
curl -X POST https://rag.mycorp.com/users/ \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "display_name": "Alice Cooper", "external_user_id": "alice@mycorp.com", "email": "alice@mycorp.com", "is_admin": false }'external_user_idmust equal the user’s OIDCsub. If you don’t know it, ask the admin to check with a test login (thesubis the.subclaim in the ID token).emailis optional metadata; not used for matching.is_admin: truegrants full admin rights inside OpenRag.
If a user tries to log in and their sub isn’t pre-provisioned, OpenRag returns 403 User not registered and logs the sub so you can complete provisioning.
Step 6 — Start and test
Section titled “Step 6 — Start and test”docker compose up --build -d# Watch the startup logs for "OIDC authentication mode enabled"docker compose logs openrag --tail 50 | grep -i OIDCOpen your browser at https://rag.mycorp.com/ → it redirects to your SSO → you log in → you come back authenticated.
If something goes wrong, see the full troubleshooting section in the OIDC guide. Most issues fall into one of three categories:
- Issuer mismatch (Step 2 — trailing slash).
- Invalid redirect URI (Step 1 — must match byte-for-byte).
- User not registered (Step 5 —
external_user_id≠sub).
Appendix — Programmatic access in SSO mode
Section titled “Appendix — Programmatic access in SSO mode”Once AUTH_MODE=oidc, human users go through SSO. CI pipelines, scripts, and external agents keep working by using the per-user bearer token (users.token) — the same one returned at POST /users/ creation. Example:
# The token printed when you created alice in Step 5:curl -H "Authorization: Bearer or-xxxxxxxxxxxxxxxx" https://rag.mycorp.com/v1/modelsThis gives you the best of both worlds: human-friendly SSO for the UI, token-based auth for automation.