LWP supports three authentication methods that can be enabled individually or combined:
| Method | Use case |
|---|---|
oidc |
Enterprise SSO — Azure AD, Okta, Google, Auth0, Authentik, Nextcloud |
local |
Username + bcrypt password stored in the LWP database |
ldap |
Bind against an existing OpenLDAP or Active Directory server |
Set AUTH_METHODS in your .env to a comma-separated list of the methods you want active:
# Show all three options on the login page
AUTH_METHODS=oidc,local,ldap
# OIDC only (default)
AUTH_METHODS=oidc
# Local accounts only (self-contained, no external IdP)
AUTH_METHODS=local
# LDAP only
AUTH_METHODS=ldap
The login page adapts automatically: a username/password form appears when local or ldap is enabled; an SSO button appears when oidc is enabled. Both can appear at the same time.
No external service required. Admin creates users via the admin panel (Users → Create local user) or the API:
POST /api/admin/users
{
"username": "alice",
"email": "alice@example.com",
"display_name": "Alice",
"password": "CorrectHorseBattery",
"is_admin": false
}
Reset a password:
POST /api/admin/users/{id}/set-password
{"password": "NewPassword123"}
Passwords are stored as bcrypt hashes (cost 12). The plain-text password is never stored or logged.
The first user created in an empty system automatically receives admin rights, regardless of auth method.
AUTH_METHODS=ldap # or oidc,ldap,local
LDAP_HOST=ldap.example.com
LDAP_PORT=389 # 636 for LDAPS
LDAP_BIND_DN=cn=lwp-service,dc=example,dc=com
LDAP_BIND_PASSWORD=service-password
LDAP_BASE_DN=dc=example,dc=com
LDAP_USER_FILTER=(uid={username})
LDAP_USER_ATTR_EMAIL=mail
LDAP_USER_ATTR_DISPLAY_NAME=cn
LDAP_TLS=none # none | ldaps | starttls
LDAP_GROUPS_ATTR=memberOf
LDAP_USER_FILTER=(uid={username})
LDAP_USER_ATTR_EMAIL=mail
LDAP_USER_ATTR_DISPLAY_NAME=cn
LDAP_GROUPS_ATTR=memberOf
LDAP_HOST=dc01.corp.example.com
LDAP_PORT=636
LDAP_TLS=ldaps
LDAP_BIND_DN=CN=lwp-svc,OU=Service Accounts,DC=corp,DC=example,DC=com
LDAP_BIND_PASSWORD=service-password
LDAP_BASE_DN=DC=corp,DC=example,DC=com
LDAP_USER_FILTER=(sAMAccountName={username})
LDAP_USER_ATTR_EMAIL=userPrincipalName
LDAP_USER_ATTR_DISPLAY_NAME=displayName
LDAP_GROUPS_ATTR=memberOf
LDAP_BIND_DN).LDAP_BASE_DN using LDAP_USER_FILTER (replaces {username} with the login input).LDAP_USER_ATTR_EMAIL, LDAP_USER_ATTR_DISPLAY_NAME, and LDAP_GROUPS_ATTR.auth_source = "ldap"). Passwords are never stored locally for LDAP users.memberOf are synced to LWP groups (auto-created if missing).LDAP users must re-authenticate to LDAP on every login — LWP only stores session JWTs, not LDAP credentials.
Works with any standards-compliant OIDC provider. The backend is a pure OIDC client (authlib); no local Keycloak or identity server is needed.
OIDC_ISSUER=https://...
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_SCOPES=openid email profile groups
OIDC_GROUPS_CLAIM=groups
Configure the callback URI at your provider:
http(s)://your-lwp-domain/api/auth/oidc/callback
https://lwp.example.com/api/auth/oidc/callbackOIDC_ISSUER=https://accounts.google.com
OIDC_SCOPES=openid email profile
Groups are not available from Google’s OIDC endpoint without Admin SDK. Leave OIDC_GROUPS_CLAIM blank.
https://lwp.example.com/api/auth/oidc/callbackgroups (access token)OIDC_ISSUER=https://login.microsoftonline.com/{tenant-id}/v2.0
OIDC_SCOPES=openid email profile
OIDC_GROUPS_CLAIM=groups
https://lwp.example.com/api/auth/oidc/callbackgroups claim to ID tokenOIDC_ISSUER=https://your-org.okta.com
OIDC_SCOPES=openid email profile groups
OIDC_GROUPS_CLAIM=groups
https://lwp.example.com/api/auth/oidc/callbackgroups to ID tokenOIDC_ISSUER=https://your-tenant.auth0.com/
OIDC_SCOPES=openid email profile
OIDC_GROUPS_CLAIM=https://lwp/groups # adjust to your namespace
https://lwp.example.com/api/auth/oidc/callbackopenid email profile; add a custom scope for groups if neededOIDC_ISSUER=https://auth.example.com/application/o/{slug}/
OIDC_SCOPES=openid email profile
OIDC_GROUPS_CLAIM=groups
If Nextcloud is already your storage backend, install its oidc app (Nextcloud →
Apps → “OpenID Connect Provider”) to turn the same Nextcloud instance into your
OIDC identity provider. Users then log into LWP with their Nextcloud account —
one login, one identity, no separate IdP to run.
oidc).LWP, redirect URI
https://lwp.example.com/api/auth/oidc/callback. Note the generated
client ID/secret.OIDC_ISSUER=https://cloud.example.com
OIDC_CLIENT_ID=<from the NC oidc app>
OIDC_CLIENT_SECRET=<from the NC oidc app>
OIDC_SCOPES=openid email profile groups
OIDC_GROUPS_CLAIM=groups
This is the almost-seamless setup: pair it with Nextcloud auto-mount via OIDC below and the same login both authenticates the user and mounts their Nextcloud storage — no admin credentials, no separate account linking, because the IdP and the storage backend are the same account.
All three auth methods sync groups to LWP:
OIDC_GROUPS_CLAIM claim (a JSON array of strings).LDAP_GROUPS_ATTR (memberOf values like CN=IT,OU=Groups,... → IT).Groups are created in LWP if they don’t exist. Membership is updated on every login. Use LWP groups to control image access via Admin → Images → Permissions.
When users sign in with OIDC and Nextcloud trusts the same identity provider (via NC’s user_oidc app), LWP can provision each user’s Nextcloud mount automatically — no admin credentials and no per-user first-run setup.
How it works (approach B): on first OIDC login, LWP uses the user’s OIDC access token as a Bearer against Nextcloud to:
GET /ocs/v2.php/cloud/user), andGET /ocs/v2.php/core/getapppassword).The app password is stored encrypted (User.nc_password_enc) and used for the rclone WebDAV mount from then on (basic auth — durable, survives token expiry). Subsequent logins skip provisioning if a password is already stored.
Admin → Settings → Nextcloud → Auto-provision via OIDC. Set the Nextcloud URL; admin username/password are not required for this mode.
user_oidc app, pointed at the same issuer as OIDC_ISSUER, and configured to accept bearer tokens for OCS/DAV requests.preferred_username; NC’s user_oidc should map the same claim so the resolved cloud/user id matches.Provisioning is best-effort — if the bearer call is rejected, login still succeeds and the mount is simply skipped (falls back to admin-provisioning or per-user config). Check backend logs for OIDC Nextcloud provisioning failed.
TOTP 2FA is available for local and LDAP users (not OIDC — those users rely on the IdP for MFA). It uses the TOTP standard (RFC 6238), compatible with Google Authenticator, Authy, 1Password, Bitwarden, and any other TOTP app.
From this point on, login requires password + TOTP code.
POST /api/auth/login {username, password}
→ {requires_totp: true, totp_token: "<short-lived JWT>"}
POST /api/auth/2fa/verify {totp_token: "…", code: "123456"}
→ sets HttpOnly JWT cookies — user is logged in
The totp_token is a short-lived (2-minute) JWT used only to carry the pending-auth state between the two calls. It does not grant any access.
Profile → Security → Remove two-factor authentication (requires current valid TOTP code).
Or as admin: Admin → Users → Edit → Security → Disable 2FA (no code required).
| Detail | Value |
|---|---|
| Algorithm | TOTP (RFC 6238), HMAC-SHA1, 6 digits, 30 s window |
| Secret storage | Fernet-encrypted in users.totp_secret_enc column |
| Setup flow | Secret stored in users.totp_pending_enc until confirmed |
| Clock tolerance | ±1 window (accepts codes 30 s early or late) |
TOTP secrets are Fernet-encrypted with a key derived from SECRET_KEY. If SECRET_KEY changes, existing TOTP secrets become unreadable — users will need to set up 2FA again.
The very first user to log in (regardless of auth method) is automatically granted is_admin = true. Use this account to configure the system. Subsequent users are regular users by default.