Skip to main content

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:

  1. Libre WebUI shows the first-time setup flow.
  2. The user creates the first local account.
  3. The account is assigned the admin role.
  4. 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 202 with approvalRequired: true, and the UI explains that an administrator has to approve the account.
  • A password sign-in with correct credentials is refused with 403 and the code ACCOUNT_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 active status.

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 the user role 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

RolePurpose
adminInstance administration, user management, system settings, and trusted Work runtime operation
userNormal 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_DIR on persistent, access-controlled storage.
  • Back up ENCRYPTION_KEY with 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.