Skip to main content

Authentication

Auth is required by default. There is no step where the server is briefly open while you configure it.

First boot

On the first start with an empty database, the server creates an administrator and generates a one-time password. It appears in two places:

  • <dataDir>/initial-admin-password, mode 0600
  • the server log, at warn level

Read it, log in, and complete setup. The file is deleted when setup succeeds.

$ docker compose exec flowwright cat /data/.flowwright/initial-admin-password
warning

The file is removed only on successful setup — there is no expiry. If you never finish onboarding it stays on disk indefinitely. And because the password is also in the log, anywhere you ship logs has a copy: complete setup promptly, or delete the file and re-bootstrap from an empty database.

This happens only when there are zero users and auth is not disabled. It is not a per-boot event.

The two modes

FW_AUTH (or security.authMode) takes required or disabled. There is no middle setting.

disabled is not "relaxed" — it removes the identity boundary. Every caller resolves to an implicit admin, and stored tokens are ignored entirely. The server says so at startup:

auth is disabled — anyone with network access can run commands and change projects; all users are implicit admins and stored tokens are ignored

Reasonable on a laptop. Never on anything reachable.

Sessions and tokens

Sessions are for people. Logging in sets an HttpOnly fw_session cookie with a 7-day lifetime, SameSite=Lax, and Secure when the request arrived over TLS. Passwords are stored as scrypt hashes and compared in constant time; the login path hashes a dummy value on a missing user so timing doesn't reveal who exists.

Bearer tokens are for automation. Authorization: Bearer <token>. Only the SHA-256 hash is stored — a token is shown once at creation and is unrecoverable afterward, so rotation means issuing a new one. Bearer principals never participate in interactive MFA.

Service accounts are password-less token identities (kind: service). A workspace-scoped one collapses to exactly workspace:read regardless of its stored role — scope beats role, so a scoped token can't be escalated by changing the role.

Roles

Three, each a superset of the last.

RoleCan
viewerRead runs, projects, workers, settings, workspace
member…plus trigger, cancel and retry runs, and read credentials
adminEverything — 21 permissions including settings:admin and users:admin

Authorization is per-permission, not per-role; the roles are presets over the same permission set. Every one of them is listed on Permissions.

This role is not the whole picture. It decides which routes an account may reach; what it may then do to a particular workspace's projects, runs and credentials comes from a second, per-workspace role — see Members and roles.

Enforcement is one guard in front of every route. Any /api/* route is authenticated by default — a route is anonymous only if it explicitly opts out, which /healthz and /api/health do. Routes that declare a permission additionally check it, and a principal flagged for a forced password change is refused everywhere until it changes.

Creating users

Three ways, no CLI command:

  • The web UI, as an admin — see Administration
  • The admin API
  • resources.users in the config file — declarative, and how you'd manage a fleet

A user created declaratively gets one-time credentials logged the same way the bootstrap admin does. Passwords and tokens cannot be declared in the file.

FW_REGISTRATION_MODE=open lets any visitor self-register as a member. It's closed by default and should stay that way on anything public.

MFA

FW_MFA_POLICY takes optional (default), required_privileged — admins must enrol — or required_all. Enforced on interactive sessions; bearer tokens are unaffected, since there's no one there to prompt.