Appearance
Authentication
Sulu has two authentication paths.
1. Bearer JWT (UI)
The web UI authenticates with a stateless Bearer JWT. POST /api/auth/login returns a short-lived in-memory access token and sets a rotating, HttpOnly sulu_refresh cookie (SameSite=Strict, Path=/api/auth); the app sends Authorization: Bearer <token> on every request and silently calls POST /api/auth/refresh when the token expires. There is no server-side session and no JSESSIONID.
POST /api/auth/login { username, password }— returns the access token and sets the refresh cookie.POST /api/auth/refresh— rotates the refresh cookie and returns a fresh access token. The app calls this automatically when the access token expires.POST /api/auth/logout— revokes the refresh-token family and clears the cookie.GET /api/auth/me— returns the current user.
TIP
The username field accepts an email — Sulu's login form is email-only on the UI.
2. Bearer token (SDKs / CI)
The SDKs and CI runners send a user-scoped Bearer token via the Authorization header:
Authorization: Bearer your-token-hereTokens are validated by ApiTokenAuthFilter. A token is user-scoped — one token works across every project the user can access; per-project read/write is checked at request time against the user's role.
When to use which
- Bearer JWT (UI) — interactive (browser, automation that drives the UI).
- Bearer token (API) — non-interactive (SDKs, CI runners, scripts).
Mixing the two on a single request is unsupported.
Self-host requirements
Stateless sign-in needs two backing services beyond Postgres:
- Redis — stores refresh-token state and powers token-family reuse detection (
SULU_REDIS_*). - RabbitMQ with
rabbitmq_stomp— relays WebSocket fan-out across backend replicas (SULU_RABBITMQ_STOMP_*).
Set these env vars on the backend:
bash
SULU_JWT_SECRET=<at-least-32-bytes> # required — the backend fails to start if shorter
SULU_ACCESS_TTL=15m # access-token lifetime (default)
SULU_REFRESH_TTL=30d # refresh-token lifetime (default)LDAP / Active Directory (on-premise)
Sulu can delegate authentication to a corporate LDAP or Active Directory server. Users authenticate with their corporate credentials; Sulu auto-creates a local user record on first login and keeps group memberships in sync.
Operator setup
Set the following env vars on the backend container:
bash
SULU_AUTH_LDAP_ENABLED=true
SULU_LDAP_URL=ldap://ldap.yourcompany.com:389 # ldaps://... for TLS
SULU_LDAP_BASE_DN=dc=yourcompany,dc=com
SULU_LDAP_MANAGER_DN=cn=admin,dc=yourcompany,dc=com
SULU_LDAP_MANAGER_PASSWORD=<readonly-service-account-password>
SULU_LDAP_USER_SEARCH_FILTER=(uid={0}) # for AD: (sAMAccountName={0})
# Optional (sensible defaults):
SULU_LDAP_USER_SEARCH_BASE=ou=users,dc=yourcompany,dc=com # default = BASE_DN
SULU_LDAP_GROUP_SEARCH_BASE=ou=groups,dc=yourcompany,dc=com # default = BASE_DN
SULU_LDAP_GROUP_SEARCH_FILTER=(member={0}) # defaultGroup synchronisation
LDAP groups are mapped onto Sulu Groups by case-sensitive name match. Before enabling LDAP, create the corresponding Sulu Groups in Admin → Groups so they have defined project-level role bindings. Groups missing from Sulu are silently skipped on sync.
Bootstrap admin fallback
SULU_BOOTSTRAP_ADMIN_EMAIL / SULU_BOOTSTRAP_ADMIN_PASSWORD continue to work even when LDAP is enabled. If your LDAP is down, you can still log in as the bootstrap admin and disable LDAP via env restart.
Two-factor authentication (TOTP)
Sulu supports per-user 2FA via TOTP (RFC 6238) — compatible with Google Authenticator, 1Password, Authy, and any other RFC-compliant authenticator app.
Enabling 2FA on your account
- Open Profile → Security.
- Click Enable two-factor authentication.
- Scan the QR code (or paste the secret URL) into your authenticator app.
- Save the 10 recovery codes somewhere safe (a password manager). Each is one-time-use.
- Enter the 6-digit code from your authenticator to activate.
Login flow after 2FA
After 2FA is on, the login becomes two steps: enter your password, then enter the 6-digit code. If you lose your authenticator, click Use recovery code instead during the second step and enter one of your saved recovery codes.
Disabling 2FA
In Profile → Security, click Disable two-factor authentication. You will be asked to enter a current 6-digit code from your authenticator app to confirm — this prevents someone who has temporarily stolen your session from silently turning off 2FA. After successful verification, your secret and recovery codes are deleted from the database.
Rate limits
Login and 2FA endpoints are rate-limited to prevent brute-force:
/login— 60 attempts per minute/2fa/verify— 5 attempts per minute (after step 1 has been passed)/2fa/enroll,/2fa/disable,/forgot-password— small limits per user/window
If you hit a limit, wait 60 seconds and retry. Repeated hits are logged server-side and visible to platform admins in /admin/audit.