Authentication & Security
Libre WebUI uses local user accounts with JWT sessions. A fresh installation always permits one local administrator bootstrap. Public registration for every later local or OAuth account is closed by default.
First-Time Setup
When the database has no users:
- Libre WebUI shows the first-time setup flow.
- The user creates the first local account.
- The account is assigned the
adminrole. - Every later public registration stays closed unless explicitly enabled.
Existing databases keep their current users and roles.
Local Accounts
Local signup requires:
- Username
- Password between 12 characters and 72 UTF-8 bytes, with uppercase, lowercase, and a number
- Optional email
Passwords are hashed with bcrypt before storage. Login and signup routes are rate-limited.
Registration Approval
Public registration does not grant access by itself. Every account created
through the public signup form or through an OAuth provider starts in a
pending state and must be approved by an administrator before it can sign in.
The one exception is bootstrap: the first real account on an empty database is
created active with the admin role, atomically, so a fresh install still
produces a working administrator. Every later registration waits for review.
What a pending user sees:
- Signup succeeds but returns no session token. The API responds
202withapprovalRequired: true, and the UI explains that an administrator has to approve the account. - A password sign-in with correct credentials is refused with
403and the codeACCOUNT_PENDING("Your account is waiting for administrator approval"). An OAuth sign-in redirects back to the login page with?approval=pending. - Account status is re-read from the database on every authenticated request,
so a session can never outlive an account's
activestatus.
What an administrator sees:
- User management shows a Pending approvals card listing waiting accounts, each with an Activate account action and a reject action. Rejection is deletion; there is no separate suspended state.
- Administrators are notified in the app while signed in: a badge on the Users
entry and a toast when new registrations arrive. The pending-approvals
summary is polled about once a minute
(
GET /api/users/pending-approvals, admin-only). - Approval (
PATCH /api/users/:id/approve, admin-only) records which administrator approved the account and when. It does not change the role: approved accounts keep theuserrole until an administrator promotes them. Approval takes effect on the user's next sign-in attempt; nothing needs to be recreated.
Existing accounts are unaffected by an upgrade: only accounts created through public registration after the feature shipped start as pending. Accounts created by an administrator from user management are active immediately.
Enable public registration deliberately
Registration defaults to disabled. Set the backend environment variable below only while new local or OAuth accounts should be accepted:
ENABLE_SIGNUP=true
Return it to false after any planned registration window.
Existing local and OAuth users can still sign in, and administrators can still
create accounts from user management while public registration is closed.
An empty database always permits one local administrator, even when
ENABLE_SIGNUP=false; OAuth cannot claim that bootstrap slot. For a private
remote deployment, place the hostname behind an identity allowlist such as
Cloudflare Access before starting the application, then create the initial
administrator through that protected route.
Roles
| Role | Purpose |
|---|---|
admin | Instance administration, user management, system settings, and trusted Work runtime operation |
user | Normal chat, model, persona, document, and settings workflows |
Model installation, deletion, copying, pushing, and unloading are restricted to administrators because these operations change host resources.
Work Access
Work is restricted to administrators because it lets a selected model execute arbitrary commands inside a managed container. Treat every administrator with Work access as a trusted runtime operator, not only as a WebUI settings administrator.
Admin authorization is checked against the current database role rather than only the role cached in an existing JWT. Demoting an administrator therefore revokes Work access immediately. The backend then attempts to abort active runs and stop the user's Work containers and previews while preserving task records and named volumes. If Docker cleanup fails, access remains revoked, the role change reports the cleanup failure, and the operator must restore Docker access and retry cleanup.
Deleting a user is destructive for that user's Work data. Libre WebUI first stops their managed containers and removes their Work volumes, then deletes the account and database records. If Docker cannot prove that cleanup succeeded, the account deletion fails so an administrator can correct the runtime problem and retry.
Sessions
The backend signs JWTs with JWT_SECRET. Set a stable secret in production:
JWT_SECRET=replace-with-a-long-random-secret
Changing JWT_SECRET invalidates existing sessions. Local login tokens are currently issued by the main auth service with a 24-hour expiration.
Cloudflare Turnstile
Turnstile protects password login and signup when both keys are configured:
TURNSTILE_SITE_KEY=...
TURNSTILE_SECRET_KEY=...
TURNSTILE_EXPECTED_HOSTNAME=chat.example.com
The frontend assigns distinct login and signup actions. The backend verifies
the token with Cloudflare and rejects a response whose hostname or action does
not match the request. BASE_URL supplies the expected hostname when
TURNSTILE_EXPECTED_HOSTNAME is not set explicitly.
If either key is missing, Turnstile is disabled.
GitHub OAuth
Configure:
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
GITHUB_CALLBACK_URL=https://your-domain.example/api/auth/oauth/github/callback
The GitHub OAuth flow creates local users with gh_-prefixed usernames and assigns the user role by default.
Hugging Face OAuth
Configure:
HUGGINGFACE_CLIENT_ID=...
HUGGINGFACE_CLIENT_SECRET=...
HUGGINGFACE_CALLBACK_URL=https://your-domain.example/api/auth/oauth/huggingface/callback
The Hugging Face OAuth flow creates local users with hf_-prefixed usernames and assigns the user role by default.
Both OAuth providers use a cryptographically random state value bound to a
short-lived HttpOnly, SameSite cookie. The callback rejects missing or mismatched
state. After a successful callback, the JWT crosses back to the frontend in a
60-second HttpOnly cookie that is exchanged and cleared immediately; bearer
tokens are never placed in callback URLs, browser history, or referrer headers.
Redirects and CORS
Set BASE_URL for callback defaults and CORS_ORIGIN for browser access:
BASE_URL=https://your-domain.example
CORS_ORIGIN=https://your-domain.example
For local development, include the Vite dev origin:
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
Demo Mode
Demo mode is a frontend preview mode. It pre-fills disabled demo credentials and uses mock API responses. It is not a production authentication mode.
Security Checklist
- Set a strong
JWT_SECRET. - Keep
DATA_DIRon persistent, access-controlled storage. - Back up
ENCRYPTION_KEYwith the database. - Configure Turnstile for public signup.
- Use HTTPS for public deployments.
- Restrict provider API keys to the minimum scope needed.
- Keep OAuth callback URLs exact.
- Grant Work-capable administrator accounts only to people trusted to operate the backend's container runtime.