π Authentication & Authorization Overview
This document explains how user authentication and access control work within the application.
It covers admin behavior, user tokens, and partition-level permissions.
1. Authentication Activation
Section titled β1. Authentication ActivationβAUTH_TOKEN
Section titled βAUTH_TOKENβAUTH_TOKENis the token used to bootstrap the admin user and authenticate protected API calls.- In
AUTH_MODE=token, ifAUTH_TOKENis absent, OpenRAG fails closed by default. - In
AUTH_MODE=oidc, human login uses the OIDC session flow;AUTH_TOKENis not the OIDC login mechanism. - Local open mode requires
ALLOW_NO_AUTH=truetogether withAUTH_MODE=token. This should never be used in production.
2. Admin Bootstrapping
Section titled β2. Admin BootstrappingβWhen AUTH_TOKEN is set:
- On startup, the application checks whether an admin user already exists in the database.
- If not, it creates one automatically:
display_name:"Admin"is_admin:Truetoken: SHA-256 hash of theAUTH_TOKENvalue
This admin user serves as the global entry point for bootstrapping the system.
Operator-managed accounts (auth.seed_users)
Section titled βOperator-managed accounts (auth.seed_users)βService accounts whose token is decided outside OpenRAG (typically in OpenBao, shared with
the client that uses it) can be declared in the configuration instead of being created
through POST /users/. The API provisions them at every startup, right after the admin
account and the default partition:
auth: seed_users: - external_user_id: svc-cozy-stack # stable key, matched on every startup display_name: cozy-stack token_env: COZY_STACK_TOKEN # name of the env var holding the token, never the value is_admin: false partitions: - { name: twake, role: editor }The accountβs shape lives in the configuration, the token stays in the environment. With
cozy-stack, both sides read the same OpenBao key: OpenRAG gets it as COZY_STACK_TOKEN,
cozy-stack puts the same value in the rag: section of its configuration, so the two
cannot drift apart. Rotating the token is: change it in OpenBao, restart the API.
On a deployment where conf/config.yaml is baked into the image (the Helm chart), pass
the same list as JSON in the SEED_USERS environment variable, which replaces
auth.seed_users. The chart renders it from openrag.seedUsers (see
Kubernetes).
Behaviour on each startup:
- Token env var set: the account is created, or updated, matched on
external_user_id. Its stored hash is rewritten, its display name and admin flag follow the configuration and the listed memberships are created or set to the listed role. The one exception: an account that is the only owner of a listed partition (it created it through the API after a first startup that did not find it) stays its owner, with a warning, since demoting it would leave nobody to manage the partitionβs members. Memberships the entry does not list are never touched. The token it replaces stops working before the new one is written, so two entries can also swap tokens. - Token env var unset or empty: the stored token is kept and a warning is logged, but the admin flag and the listed roles are still applied, so a right taken away in the configuration does not wait for the secret. A new account is not created, since it would have no credential. Startup carries on, so deployments that do not provide the variable keep working.
- Entry removed from the configuration: the accountβs token is revoked (cleared, so no bearer matches it), its admin flag is dropped and a warning is logged, at every startup. The row is kept, together with its memberships and what it uploaded, so partitions it owns are not left without an owner. It stays managed: listing it again with a token restores it, and the admin API refuses to give it a token or admin rights (409), since the next startup would take them away again. Delete it once it is no longer needed.
- Partition that does not exist: that membership is skipped with a warning. Partitions are never created by this step.
is_admin: true: honoured, and a warning is logged on every startup.
Seeding never deletes an account or a membership: removing a partition from an entry, or
the entry itself, leaves the existing memberships in place. To take access away, remove
the membership through the API (DELETE /partition/{partition}/users/{user_id}).
What seeding refuses, each time with an error naming the entry and its env var, never the token or its hash:
- An account created another way (through the API, or by an OIDC login, which stores the
IdP
subinexternal_user_id) is never taken over, even when itsexternal_user_idmatches an entry. Only accounts created by this step are managed by it, and the admin account (users.id = 1) never is. Pick identifiers that cannot be an IdPsub, such as asvc-prefix. - A token that is a published example value or shorter than 12 characters, the same rules
as
AUTH_TOKEN(ALLOW_INSECURE_SECRETS=truedowngrades this to a warning on a disposable stack). - A token equal to
AUTH_TOKEN, to another entryβs token (both entries are refused), or to the token of another existing account.
A refused token is not written. On an existing account the previous token hash stays in
place, while the admin flag and listed roles are still applied: a rotation to a refused
value leaves the old token valid until a startup with an accepted one. This is deliberate, as for an unset token env var: a mistake in the
new secret does not lock out the client still using the old one. When the old token must
stop working at once (it leaked, for instance), do not rely on the rotation: call
POST /users/{user_id}/regenerate_token, which replaces it immediately, or remove the
entry from auth.seed_users and restart, which revokes it. Then fix the secret and
restart.
The configuration itself is validated when it is loaded, and an error stops the boot:
role must be owner, editor or viewer, external_user_id and token_env must be
unique across entries, a partition may be listed once per entry, token_env cannot be
AUTH_TOKEN, and unknown keys (a plaintext token: for instance) are rejected. Unknown
keys directly under auth: are rejected too: a misspelt seed_users (such as the chartβs
seedUsers) would otherwise load as an empty list, and the next startup would revoke
every managed account.
If AUTH_TOKEN is set to the token a managed account already holds (both read from the
same secret by mistake), the admin keeps it: the managed accountβs token is cleared at
startup with a warning, and its entryβs token is then refused as above.
These accounts are for bearer tokens only. An OIDC login whose sub equals the
external_user_id of a managed account is refused with a 403 before anything is written
to the account, and any OIDC session found on a managed account is deleted at the next
startup.
The configuration stays the source of truth for these accounts. Changing the
external_user_id or the admin flag of a managed account through the admin API is refused
with a 409: seeding matches rows on external_user_id, so a renamed row would drop out of
its entry, and it sets the admin flag at every startup. Other admin edits (display name,
the role on a listed partition, a regenerated token) are accepted but only last until the
next startup that has the entryβs token, which rewrites them from the configuration. Make
those changes in auth.seed_users instead.
Memberships on partitions the entry does not list are left alone.
Deleting a managed account through DELETE /users/{user_id} is refused with a 409 while
its entry is still in auth.seed_users: the delete would remove every partition the
account owns, and the next startup would recreate the account under a new id with only
its listed memberships. To delete it, remove the entry from auth.seed_users and restart
the API (the accountβs token is revoked), then delete it.
If the seeding step itself fails (a database error, for instance), the error is logged without the token or the driverβs message and the API starts anyway. What takes access away (revoking removed entries, ending OIDC sessions, clearing a token about to be replaced) is committed first, so a failure later in the step cannot bring it back; the accounts being created or updated keep their previous state until the next startup.
Only the API process provisions these accounts. Several replicas starting together apply the list one after the other, under a database lock. The Chainlit and MCP processes never write them.
Each process applies the configuration it booted with, so the last one to start wins. A replica still on the old configuration or token during a rollout, or a Ray pod that was not restarted, re-applies the old list or token hash when it boots. After changing the seed users or a token, complete the rollout and restart the Ray pods.
The token is hashed exactly as the environment variable holds it, as for AUTH_TOKEN: a
trailing newline in the Secret becomes part of the token and the clientβs bearer no longer
matches.
3. Token Management
Section titled β3. Token ManagementβGeneration
Section titled βGenerationβ- Each new user is assigned a token at creation time (format:
or-<random hex>). - The app returns the raw token to the API caller once (e.g.,
POST /usersresponse).
Storage
Section titled βStorageβ- Only a SHA-256 hash of the token is stored in PostgreSQL.
- The raw token is never persisted, ensuring that leaked database contents cannot reveal user credentials.
Validation
Section titled βValidationβ- When an API request includes an
Authorization: Bearer <token>header:- The middleware extracts the token.
- The hash of this token is computed.
- The hash is compared against the stored value in the
userstable.
4. User Roles
Section titled β4. User Rolesβπ Admin
Section titled βπ Adminβ- Full access to all API routes, including:
- User management
- Actor management
- Queue and system information
- Can also create other users and assign privileges.
- Admins can use the app as regular users (own partitions, files, etc.).
- By default, an admin cannot view other usersβ data.
π§ Super Admin Mode
Section titled βπ§ Super Admin Modeβ- Controlled by the environment variable
SUPER_ADMIN_MODE. - When
SUPER_ADMIN_MODE=true:- The admin can access all partitions and data across users.
- Partition-level access restrictions are ignored.
- When
SUPER_ADMIN_MODE=false:- Admin privileges are limited to admin-only operations (user creation, actor management, etc.).
- Data-level access (partitions/files) requires using a normal user account.
5. Regular Users
Section titled β5. Regular Usersβ- Created by an admin via the
/usersendpoint. - Receive a personal API token (returned once upon creation).
- Can authenticate using
Authorization: Bearer <token>.
Users can:
- Create and manage their own partitions and files.
- Access shared partitions based on assigned roles.
6. Partition Access Roles
Section titled β6. Partition Access RolesβAccess control is handled through the partition_memberships table.
Each userβpartition relationship defines a role:
| Role | Description | Capabilities |
|---|---|---|
| owner | Partition creator or owner | Full access β can delete the partition, manage members, edit files, etc. |
| editor | Collaborator | Can read and write files within the partition |
| viewer | Read-only member | Can view content and perform semantic search or chat but not modify data |
Role-based restrictions are enforced via dependency guards:
require_partition_ownerrequire_partition_editorrequire_partition_viewer
7. Authorization Flow Summary
Section titled β7. Authorization Flow Summaryβ- Request arrives with optional
Authorization: Bearer <token>. - In
AUTH_MODE=token, ifAUTH_TOKENis unset, authentication is rejected unlessALLOW_NO_AUTH=trueis explicitly enabled for local development. - If a token is configured:
- Middleware hashes the token.
- Looks up the user by hash.
- Loads their partition memberships.
- User info and memberships are attached to
request.state. - Role-based dependencies ensure the user has proper privileges before executing the endpoint logic.
8. Summary Diagram
Section titled β8. Summary Diagramβββββββββββββββββββββββββββ Incoming Request ββ Authorization: Bearer βββββββββββββββ¬βββββββββββ β βΌββββββββββββββββββββββββββββββ AuthMiddleware ββ - Hash token (SHA-256) ββ - Lookup user in DB ββ - Load memberships ββ - Attach to request.state βββββββββββββββ¬βββββββββββββββ β βΌβββββββββββββββββββββββββββββββββ Endpoint Dependency Checks ββ (e.g., require_partition_*) βββββββββββββββ¬ββββββββββββββββββ β βΌβββββββββββββββββββββββββββββββββ Route Logic Executes ββ with validated user context βββββββββββββββββββββββββββββββββ9. Security Highlights
Section titled β9. Security Highlightsβ- No plaintext tokens stored in database.
- SHA-256 hashing for authentication.
- Partition-based role hierarchy for fine-grained access control.
- Admin privileges separated from regular user data access.
- Configurable
SUPER_ADMIN_MODEfor system-wide debugging or admin override.