Chuyển tới nội dung chính

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 by default because it lets a selected model execute arbitrary commands inside a managed container. An administrator can open Work to all active users from the User Management page; the setting persists across restarts and takes effect immediately, including for open terminal sessions. Host-folder workspaces remain admin-only in every mode because they bind-mount server paths. Treat everyone granted Work access as a trusted runtime operator, not only as a WebUI user.

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.

Groups and Resource Grants

Administrators can create groups and manage memberships from the User Management page. Groups are principals for resource grants: the owner of a chat, note, document, knowledge collection, folder, persona, prompt, skill, or calendar can grant read, write, or admin access to a user or a group through the access API — every shareable surface uses the same share dialog (see Sharing) — and administrators can scope registered tool servers to users or groups the same way. Resources stay private by default — the global admin role does not grant access to other users' content. Membership is evaluated at request time, so removing a member revokes group-granted access immediately. The "effective access" view on the User Management page answers "why can this user access this?" by listing their role, groups, feature access, and every grant that reaches them.

Security Audit Log

Security-sensitive actions — logins and failures, logouts, session and token revocations, user, group, grant, and token changes — are recorded in an append-only audit log that is separate from usage analytics. Details are redacted before they are stored: secret-like keys are dropped and payload sizes are capped, so passwords, tokens, and prompt content never enter the log. Group and grant mutations write their audit event inside the same database transaction, so a change cannot exist without its trail. Administrators can query the log from the User Management page; retention defaults to 180 days (AUDIT_RETENTION_DAYS).

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 and OAuth login tokens use JWT_EXPIRES_IN, which defaults to 7d; changing that value affects new sessions. WebSocket connections exchange the durable token for a short-lived, one-use ticket and close when the underlying session expires.

Every login also creates a server-side session record bound into the JWT. Settings → Sessions lists each device with its sign-in method, first and last activity, and expiry. Revoking a session there (or "Sign out other sessions") invalidates its token immediately on every replica and closes its live WebSocket connections; logout revokes the current session the same way. Tokens issued before this feature carry no session id and remain valid until expiry, except that "sign out other sessions" from a fresh login also stamps a per-account cutoff that rejects them.

API Tokens

Settings → API keys mints personal access tokens (prefix lwk_) for programmatic use. The secret is shown once and stored only as a hash. Each token carries an explicit scope list (chat, models, documents, notes, personas, media, work, admin); the backend maps every route family to a required scope, so a notes-only token cannot touch chats or administration, and session management is never reachable with a token. Tokens support optional expiry, track last use, can be revoked at any time, and are rate-limited per token across replicas. Admin-scoped tokens can be minted only by administrators and still require the account to hold the admin role when used. A chat-scoped token is also the key for the OpenAI-compatible public /v1 API.

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 access (administrator accounts, or the open-to-all-users mode) only to people trusted to operate the backend's container runtime.