Skip to content

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 .env variables. 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 running openrag/api.py). If you point the IdP at the front URL instead, you get an infinite redirect loop: the front has no /auth/callback route, 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) vs https://ui.mycorp.com (the front). Use the backend URL for the OIDC endpoints below.

FieldValue to give
Client typeconfidential (server-to-server token exchange)
Grant typeauthorization_code
Response typecode
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 URIsan optional URL outside OpenRag (see §Step 4 below)
Allowed scopesopenid, 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:

  1. client_id — a public identifier, typically openrag or similar.
  2. client_secret — a long random string, shown only once by most IdPs. Store it in a password manager.
  3. The IdP issuer URL — e.g. https://sso.mycorp.com/ or https://keycloak.mycorp.com/realms/mycorp.

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:

Terminal window
curl -s https://sso.mycorp.com/.well-known/openid-configuration | jq -r .issuer

Copy 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:

Terminal window
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.


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=openrag
OIDC_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:email

Each 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.


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=true in your .env. The callback then creates a non-admin user from the ID-token claims on first login and keeps display_name + email in sync with the IdP on every subsequent login. The trade-off: your IdP’s user list becomes the source of truth for OpenRag accounts. See the Auto-provisioning section of the OIDC guide for the full trust-model.

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:

Terminal window
# 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_id must equal the user’s OIDC sub. If you don’t know it, ask the admin to check with a test login (the sub is the .sub claim in the ID token).
  • email is optional metadata; not used for matching.
  • is_admin: true grants 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.


Terminal window
docker compose up --build -d
# Watch the startup logs for "OIDC authentication mode enabled"
docker compose logs openrag --tail 50 | grep -i OIDC

Open 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:

  1. Issuer mismatch (Step 2 — trailing slash).
  2. Invalid redirect URI (Step 1 — must match byte-for-byte).
  3. User not registered (Step 5 — external_user_idsub).

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:

Terminal window
# The token printed when you created alice in Step 5:
curl -H "Authorization: Bearer or-xxxxxxxxxxxxxxxx" https://rag.mycorp.com/v1/models

This gives you the best of both worlds: human-friendly SSO for the UI, token-based auth for automation.