docs/content/features/authentication.md
+++ disableToc = false title = "Authentication & Authorization" weight = 85 url = '/features/authentication' +++
LocalAI supports two authentication modes: legacy API key authentication (simple shared keys) and a full user authentication system with roles, sessions, OAuth, and per-user usage tracking.
The simplest way to protect your LocalAI instance is with API keys. Set one or more keys via environment variable or CLI flag:
# Single key
LOCALAI_API_KEY=sk-my-secret-key localai run
# Multiple keys (comma-separated)
LOCALAI_API_KEY=key1,key2,key3 localai run
Clients provide the key via any of these methods:
Authorization: Bearer <key> headerx-api-key: <key> headerxi-api-key: <key> headertoken cookieLegacy API keys grant full admin access - there is no role separation. For multi-user deployments with role-based access, use the user authentication system instead.
API keys can also be managed at runtime through the [Runtime Settings]({{%relref "features/runtime-settings" %}}) interface.
When you configure database authentication or legacy API keys, LocalAI makes the HTTP surface private by default. An anonymous request succeeds only for an approved method and path or an explicit deployment override. If you configure neither authentication mode, the authentication middleware does not restrict requests.
The following discovery requests are available anonymously:
GET /.well-known/localai.jsonGET /api/instructionsGET /api/instructions/{name}GET requests at /swagger and under /swagger/These discovery APIs describe the server's API surface. The endpoints that they advertise still require credentials unless this section lists them as public or bootstrap routes.
LocalAI also permits the requests needed for health checks, credential acquisition, and the login UI. These routes do not make the rest of the API public.
GET /healthz and GET /readyz.GET /api/auth/status and POST /api/auth/token-login.POST /api/auth/register and POST /api/auth/login.GET /api/auth/github/login and GET /api/auth/github/callback.GET /api/auth/oidc/login and GET /api/auth/oidc/callback.OPTIONS under /api/auth/.GET /, HEAD /, and GET requests at /app, /browse, /login, /invite/*, and /explorer. Subpaths under /app/ and /browse/ are also available through GET.GET /favicon.svg and GET requests under /assets/, /locales/, and /static/.GET /api/branding and GET requests under /branding/asset/. Branding mutations still require admin credentials.Without an explicit deployment override, every other route requires credentials. This includes GET /version, all model and backend API routes, and all inference routes. MCP and moderation aliases are also private:
POST /v1/mcp/chat/completions, POST /mcp/v1/chat/completions, and POST /mcp/chat/completionsPOST /v1/moderations and POST /moderationsGenerated output URLs also require credentials. This applies to every URL under /generated-audio/, /generated-images/, /generated-videos/, and /generated-3d/.
Embedded deployments can add path prefixes to ApplicationConfig.PathWithoutAuth. Each prefix bypasses global authentication for every HTTP method below that prefix. Route-specific authorization still applies when the route registers it. Keep these overrides as narrow as possible; the default list is empty.
Two legacy flags provide a separate GET-only compatibility override when legacy API keys are configured:
LOCALAI_DISABLE_API_KEY_REQUIREMENT_FOR_HTTP_GET=true enables the override.LOCALAI_HTTP_GET_EXEMPTED_ENDPOINTS sets the regular expressions for the exempt GET routes.The endpoint expressions have no effect unless you enable LOCALAI_DISABLE_API_KEY_REQUIREMENT_FOR_HTTP_GET. Review custom expressions carefully because they can expose protected reads.
The user authentication system provides:
Set LOCALAI_AUTH=true or provide a GitHub OAuth Client ID or OIDC Client ID (which auto-enables auth):
# Enable with SQLite (default, stored at {DataPath}/database.db)
LOCALAI_AUTH=true localai run
# Enable with GitHub OAuth
GITHUB_CLIENT_ID=your-client-id \
GITHUB_CLIENT_SECRET=your-client-secret \
LOCALAI_BASE_URL=http://localhost:8080 \
localai run
# Enable with OIDC provider (e.g. Keycloak)
LOCALAI_OIDC_ISSUER=https://keycloak.example.com/realms/myrealm \
LOCALAI_OIDC_CLIENT_ID=your-client-id \
LOCALAI_OIDC_CLIENT_SECRET=your-client-secret \
LOCALAI_BASE_URL=http://localhost:8080 \
localai run
# Enable with PostgreSQL
LOCALAI_AUTH=true \
LOCALAI_AUTH_DATABASE_URL=postgres://user:pass@host/dbname \
localai run
| Environment Variable | Default | Description |
|---|---|---|
LOCALAI_AUTH | false | Enable user authentication and authorization |
LOCALAI_AUTH_DATABASE_URL | {DataPath}/database.db | Database URL - postgres://... for PostgreSQL, or a file path for SQLite |
GITHUB_CLIENT_ID | GitHub OAuth App Client ID (auto-enables auth when set) | |
GITHUB_CLIENT_SECRET | GitHub OAuth App Client Secret | |
LOCALAI_OIDC_ISSUER | OIDC issuer URL for auto-discovery (e.g. https://accounts.google.com) | |
LOCALAI_OIDC_CLIENT_ID | OIDC Client ID (auto-enables auth when set) | |
LOCALAI_OIDC_CLIENT_SECRET | OIDC Client Secret | |
LOCALAI_BASE_URL | Base URL for OAuth callbacks (e.g. http://localhost:8080) | |
LOCALAI_ADMIN_EMAIL | Email address to auto-promote to admin role on login | |
LOCALAI_REGISTRATION_MODE | approval | Registration mode: open, approval, or invite |
LOCALAI_DISABLE_LOCAL_AUTH | false | Disable local email/password registration and login (for OAuth/OIDC-only deployments) |
Note: network-backed storage. File-based SQLite relies on POSIX file locking, which is unreliable over network filesystems (SMB/CIFS/NFS, e.g. Azure Files / Azure Container Apps shared volumes). On such storage the auth DB can fail to migrate with
database is locked. Use PostgreSQL (LOCALAI_AUTH_DATABASE_URL=postgres://...) when the data directory lives on shared or network storage, or placedatabase.dbon a local volume.
If you want to enforce OAuth/OIDC-only login and prevent users from registering or logging in with email/password, set LOCALAI_DISABLE_LOCAL_AUTH=true (or pass --disable-local-auth):
# OAuth-only setup (no email/password)
LOCALAI_DISABLE_LOCAL_AUTH=true \
GITHUB_CLIENT_ID=your-client-id \
GITHUB_CLIENT_SECRET=your-client-secret \
LOCALAI_BASE_URL=http://localhost:8080 \
localai run
When disabled:
providers list from /api/auth/status)POST /api/auth/register returns 403 ForbiddenPOST /api/auth/login returns 403 ForbiddenThere are two roles:
The first user to sign in is automatically assigned the admin role. Additional users can be promoted to admin via the admin user management API or by setting LOCALAI_ADMIN_EMAIL to their email address.
| Mode | Description |
|---|---|
open | Anyone can register and is immediately active |
approval | New users land in "pending" status until an admin approves them. If a valid invite code is provided during registration, the user is activated immediately (skipping the approval wait). (default) |
invite | Registration requires a valid invite link generated by an admin. Without one, registration is rejected. |
Admins can generate single-use, time-limited invite links from the Users → Invites tab in the web UI, or via the API:
# Create an invite link (default: expires in 7 days)
curl -X POST http://localhost:8080/api/auth/admin/invites \
-H "Authorization: Bearer <admin-key>" \
-H "Content-Type: application/json" \
-d '{"expiresInHours": 168}'
# List all invites
curl http://localhost:8080/api/auth/admin/invites \
-H "Authorization: Bearer <admin-key>"
# Revoke an unused invite
curl -X DELETE http://localhost:8080/api/auth/admin/invites/<invite-id> \
-H "Authorization: Bearer <admin-key>"
Share the invite URL (/invite/<code>) with the user. When they open it, the registration form is pre-filled with the invite code. LocalAI validates the code only when the user submits registration. Invite codes are single-use - once consumed, they cannot be reused. Expired or used invites are rejected.
For GitHub OAuth, the invite code is passed as a query parameter to the login URL (/api/auth/github/login?invite_code=<code>) and stored in a cookie during the OAuth flow.
When authentication is enabled, the following endpoints require admin role:
Model & Backend Management:
GET /api/models, POST /api/models/install/*, POST /api/models/delete/*GET /api/backends, POST /api/backends/install/*, POST /api/backends/delete/*GET /api/operations, POST /api/operations/*/cancel, POST /api/operations/*/pause, POST /api/operations/*/dismissGET /api/operations/history, DELETE /api/operations/historyGET /models/available, GET /models/galleries, GET /models/jobs/*GET /backends, GET /backends/available, GET /backends/galleriesSystem & Monitoring:
GET /api/traces, GET /api/traces/summary, GET /api/traces/{id}, POST /api/traces/clearGET /api/backend-traces, GET /api/backend-traces/{id}, POST /api/backend-traces/clearGET /api/backend-logs/*, POST /api/backend-logs/*/clearGET /api/resources, GET /api/settings, POST /api/settingsGET /system, GET /backend/monitor, POST /backend/shutdown, POST /backend/loadP2P:
GET /api/p2p/*Agents & Jobs:
/api/agents/* endpoints/api/agent/tasks/* and /api/agent/jobs/* endpointsUser-Accessible Endpoints (all authenticated users):
POST /v1/chat/completions, POST /v1/embeddings, POST /v1/completionsPOST /v1/images/generations, POST /v1/audio/*, POST /tts, POST /vad, POST /videoGET /v1/models, POST /v1/tokenize, POST /v1/detokenize, POST /v1/detectionPOST /v1/mcp/chat/completions, POST /v1/messages, POST /v1/responsesPOST /stores/*, GET /api/cors-proxyGET /version, GET /api/features, GET /metricsGET /api/auth/usage (own usage data)When auth is enabled, the React UI sidebar dynamically shows/hides sections based on the user's role:
Admin-only pages are also protected at the router level - navigating directly to an admin URL redirects non-admin users to the home page.
{LOCALAI_BASE_URL}/api/auth/github/callbackGITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variablesLOCALAI_BASE_URL to your publicly-accessible URLAny OIDC-compliant identity provider can be used for single sign-on. This includes Keycloak, Google, Okta, Authentik, Azure AD, and many others.
Steps:
{LOCALAI_BASE_URL}/api/auth/oidc/callbackLOCALAI_OIDC_ISSUER, LOCALAI_OIDC_CLIENT_ID, LOCALAI_OIDC_CLIENT_SECRETLocalAI uses OIDC auto-discovery (the /.well-known/openid-configuration endpoint) and requests the standard scopes: openid, profile, email.
Provider examples:
# Keycloak
LOCALAI_OIDC_ISSUER=https://keycloak.example.com/realms/myrealm
# Google
LOCALAI_OIDC_ISSUER=https://accounts.google.com
# Authentik
LOCALAI_OIDC_ISSUER=https://authentik.example.com/application/o/localai/
# Okta
LOCALAI_OIDC_ISSUER=https://your-org.okta.com
For OIDC, invite codes work the same way as GitHub OAuth - the invite code is passed as a query parameter to the login URL (/api/auth/oidc/login?invite_code=<code>) and stored in a cookie during the OAuth flow.
Authenticated users can create personal API keys for programmatic access:
# Create an API key (requires session auth)
curl -X POST http://localhost:8080/api/auth/api-keys \
-H "Cookie: session=<session-id>" \
-H "Content-Type: application/json" \
-d '{"name": "My Script Key"}'
User API keys inherit the creating user's role. Admin keys grant admin access; user keys grant user-level access.
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET | /api/auth/status | Auth state, current user, providers | No |
POST | /api/auth/token-login | Exchange a user or legacy API key for a browser session | No |
POST | /api/auth/register | Register with email and password | No |
POST | /api/auth/login | Log in with email and password | No |
GET | /api/auth/github/login | Start GitHub OAuth | No |
GET | /api/auth/github/callback | GitHub OAuth callback (internal) | No |
GET | /api/auth/oidc/login | Start OIDC login | No |
GET | /api/auth/oidc/callback | OIDC callback (internal) | No |
POST | /api/auth/logout | End session | Yes |
GET | /api/auth/me | Current user info | Yes |
POST | /api/auth/api-keys | Create API key | Yes |
GET | /api/auth/api-keys | List user's API keys | Yes |
DELETE | /api/auth/api-keys/:id | Revoke API key | Yes |
GET | /api/auth/usage | User's own usage stats | Yes |
GET | /api/auth/usage/sources | User's own per-API-key / per-source breakdown | Yes |
GET | /api/auth/admin/users | List all users | Admin |
PUT | /api/auth/admin/users/:id/role | Change user role | Admin |
DELETE | /api/auth/admin/users/:id | Delete user | Admin |
GET | /api/auth/admin/usage | All users' usage stats | Admin |
GET | /api/auth/admin/usage/sources | All users' per-API-key / per-source breakdown | Admin |
POST | /api/auth/admin/invites | Create invite link | Admin |
GET | /api/auth/admin/invites | List all invites | Admin |
DELETE | /api/auth/admin/invites/:id | Revoke unused invite | Admin |
When authentication is enabled, LocalAI automatically tracks per-user token usage for inference endpoints. Usage data includes:
Usage is accessible through the Usage page in the web UI (visible to all authenticated users) or via the API:
# Get your own usage (default: last 30 days)
curl http://localhost:8080/api/auth/usage?period=month \
-H "Authorization: Bearer <key>"
# Admin: get all users' usage
curl http://localhost:8080/api/auth/admin/usage?period=week \
-H "Authorization: Bearer <admin-key>"
# Admin: filter by specific user
curl "http://localhost:8080/api/auth/admin/usage?period=month&user_id=<user-id>" \
-H "Authorization: Bearer <admin-key>"
Period values:
day - last 24 hours, bucketed by hourweek - last 7 days, bucketed by daymonth - last 30 days, bucketed by day (default)all - all time, bucketed by monthResponse format:
{
"usage": [
{
"bucket": "2026-03-18",
"model": "gpt-4",
"user_id": "abc-123",
"user_name": "Alice",
"prompt_tokens": 1500,
"completion_tokens": 800,
"total_tokens": 2300,
"request_count": 12
}
],
"totals": {
"prompt_tokens": 1500,
"completion_tokens": 800,
"total_tokens": 2300,
"request_count": 12
}
}
The web UI Usage page provides:
The Sources tab on the Usage page surfaces a third dimension of the same data: traffic broken down by API key and by request source. Three source classes are tracked:
Authorization: Bearer lai-..., x-api-key, or token cookie). Each key shows up with its label (snapshotted at write time, so revoked keys still display the original name).LOCALAI_API_KEY. Visible to admins only.The Sources tab is visible to every authenticated user. Non-admins see only their own keys plus their own Web UI traffic (legacy is filtered server-side). Admins see every key from every user.
The tab is laid out as:
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/auth/usage/sources | Self | Caller's per-source breakdown. Excludes legacy. |
GET | /api/auth/admin/usage/sources | Admin | All users' per-source breakdown. Accepts user_id and api_key_id filters. Includes legacy. |
Both endpoints accept the same period parameter (day, week, month, all) as /api/auth/usage.
# Your own per-source usage for the last week
curl "http://localhost:8080/api/auth/usage/sources?period=week" \
-H "Authorization: Bearer <key>"
# Admin: filter to a single API key across all users
curl "http://localhost:8080/api/auth/admin/usage/sources?period=month&api_key_id=<key-id>" \
-H "Authorization: Bearer <admin-key>"
Response shape:
{
"buckets": [
{ "bucket": "2026-05-19", "source": "apikey",
"api_key_id": "uuid", "api_key_name": "ci-runner",
"total_tokens": 20000, "request_count": 142, "...": "..." },
{ "bucket": "2026-05-19", "source": "web",
"total_tokens": 300, "request_count": 11, "...": "..." }
],
"totals": {
"by_source": {
"apikey": { "tokens": 1234567, "requests": 8420 },
"web": { "tokens": 92000, "requests": 211 }
},
"by_key": [
{ "api_key_id": "uuid", "api_key_name": "ci-runner",
"tokens": 2100000, "requests": 8420,
"last_used": "2026-05-20T12:34:56Z" }
],
"grand_total": { "tokens": 1334777, "requests": 8645 }
},
"truncated": false
}
The by_key list is server-sorted by tokens descending and capped at 200 entries. When more keys would qualify, the response sets "truncated": true so the UI can show a notice.
Usage rows recorded before this feature have no source column. On startup, InitDB backfills them as legacy when the synthetic legacy-api-key user_id was used, and web for everything else. The migration is idempotent; existing aggregations remain correct after the upgrade.
Legacy API keys and user authentication can be used simultaneously. When both are configured:
The user authentication system requires CGO for SQLite support. It is enabled with the auth build tag, which is included by default in Docker builds.
# Building from source with auth support
GO_TAGS=auth make build
# Or directly with go build
go build -tags auth ./...
The default Dockerfile includes GO_TAGS="auth", so all Docker images ship with auth support. When building from source without the auth tag, setting LOCALAI_AUTH=true has no effect - the system operates without authentication.