Documentation
Docs Home › Manager › Authentication

Authentication

Manager authentication

Purpose

Authenticate administrators to the Manager console and /api/v1, issue a JWT for API calls, and keep the browser session on an HttpOnly cookie that is not returned in the login JSON.

Zwall does not ship OIDC, LDAP, or SAML. Do not configure those as if they were Manager features.

Audience

Operators who log in, enable TOTP, create additional administrator users, or point zwallctl at a Manager.

Edition

SHARED login, roles, and TOTP. Enterprise production rejects literal JWT/database/admin password authority in YAML and expects systemd credentials (jwt_secret, database_password, first-install admin_initial_password). Community uses operator-managed config/environment secrets.

Secret custody via scripts/setup.sh --manager-secret-custody fresh|migrate|rotate is implemented but not certified. Do not call it certified.

Status

SUPPORTED for username/password login, optional TOTP, JWT + session cookie, and the three operator-facing roles. Most operator APIs still require admin.

Prerequisites

  • Manager /health/ready returns ready
  • TLS enabled in the production example (server.tls.enabled: true); browser cookie issuance requires acceptable HTTPS per the cookie policy
  • Initial administrator credential from the supported secret contract (not from YAML in Enterprise production)

Required permissions

Scroll sideways to see the full table. Use the arrow keys when focused.

ActionAuth
POST /api/v1/auth/loginnone (rate limited)
GET /api/v1/auth/statussession cookie
POST /api/v1/auth/refreshsession
POST /api/v1/auth/logoutBearer and/or cookie (handler still reads them)
POST /api/v1/auth/change-passwordadmin
GET/POST /api/v1/auth/2fa/*admin
GET/POST/DELETE /api/v1/usersadmin + RoleAuth("admin")

Dependencies

PostgreSQL (user rows). Enterprise: systemd credential jwt_secret for token signing. Optional TOTP for accounts that have 2FA enabled.

Configuration

From configs/manager.example.yml:

Scroll sideways to see the full table. Use the arrow keys when focused.

KeyExampleSecretNotes
auth.jwt_secret""yessystemd jwt_secret in Enterprise production
auth.token_expiry8hnoadministrator/API JWT
auth.agent_token_ttl8hnocomment range 4h–8h; Agent JWT, not admin
auth.agent_refresh_before20mnoAgent rotation
auth.agent_expired_grace2mnoheartbeat-only grace
auth.default_admin.usernameadminno
auth.default_admin.password""yesfirst install only; systemd admin_initial_password

agents.auto_register must remain false in production (enrollment is a separate procedure).

Procedure — login (API)

curl -sS -X POST https://manager.example.com:8443/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"YOUR_PASSWORD"}'

If the account has TOTP enabled, include two_factor_code.

Request fields (LoginRequest):

Scroll sideways to see the full table. Use the arrow keys when focused.

FieldRequired
usernameyes
passwordyes
two_factor_codewhen 2FA is enabled on the account

Success (200) JSON:

{
  "token": "<jwt>",
  "expires_at": "2026-08-21T12:00:00Z",
  "username": "admin",
  "role": "admin"
}

The HttpOnly session_token cookie is issued for browser session bootstrap (GET /api/v1/auth/status, WebSocket). It is not placed in the JSON body. Frontends must not persist session_token in localStorage.

Use the JWT:

curl -sS https://manager.example.com:8443/api/v1/agents \
  -H "Authorization: Bearer TOKEN"

Procedure — login (zwallctl)

zwallctl login --manager-url https://manager.example.com:8443 --username admin

The password is read from the terminal when --password is omitted. --password exists; do not put it on shared shells.

Default CLI config: ~/.zwallctl/config.yml (mode 0600 when saved). Environment: ZWALL_MANAGER_URL, ZWALL_AUTH_TOKEN.

Unconfigured zwallctl defaults to http://localhost:8080. That is not the Manager example listen port (8443). Set --manager-url.

zwallctl logout

Procedure — default administrator

On Manager start, CreateDefaultAdmin creates the configured default username only if no row exists. If the row already exists, the in-memory bootstrap password is cleared and startup continues.

Enterprise first install: password comes from systemd credential admin_initial_password, then the credential is detached after readiness. YAML stays empty.

If first boot needs a password and none was supplied through the resolved credential contract, bootstrap fails closed.

Change the password after first login (POST /api/v1/auth/change-password, admin). Field names for that body are not a published DTO in this package; use the console form or inspect a live 4xx rather than inventing old_password/new_password here.

Procedure — TOTP (2FA)

Administrators can require TOTP. Endpoints (all admin-authenticated except as noted on login):

Scroll sideways to see the full table. Use the arrow keys when focused.

MethodPathNotes
GET/api/v1/auth/2fa/statuswhether 2FA is enabled for the current admin
POST/api/v1/auth/2fa/setupbody { "password": "<current>" }; response includes secret, qr_code, backup_codes — treat as secrets
POST/api/v1/auth/2fa/verifybody { "code": "<totp>" }
POST/api/v1/auth/2fa/disablebody requires password and code
POST/api/v1/auth/2fa/regenerateregenerates 2FA secrets; existence only, not a public cookbook

Login without a code on a 2FA account returns 401 {"error":"Two-factor authentication code required","requires_2fa":true}.

Procedure — users

GET /api/v1/users lists users. User JSON includes id, username, email, role, active, two_factor_enabled, created_at, last_login. Password hashes are omitted (json:"-").

POST /api/v1/users (admin) binds:

Scroll sideways to see the full table. Use the arrow keys when focused.

FieldRequired
usernameyes
emailyes (JSON bind)
passwordyes (strength-validated)
roleyes
activeoptional bind field

Operator-facing roles: admin, viewer, release_manager. Most /api/v1 operator routes still require admin. This is not a full RBAC product.

The validator also accepts campaign_operator. That string exists for in-product updater campaign permissions, which are not a supported production upgrade path. Do not staff a deployment around campaign_operator.

DELETE /api/v1/users/:id refuses deletion of the caller’s own account.

Verification

  1. Login returns 200 with token, expires_at, username, role.
  2. Set-Cookie includes HttpOnly session_token on HTTPS; JSON does not contain session_token.
  3. GET /api/v1/agents with Authorization: Bearer succeeds for admin.
  4. zwallctl ping after interactive login reaches the Manager.
  5. Wrong password → 401 {"error":"Invalid credentials"}. Inactive account → 403 {"error":"Account is inactive"}.

Expected result

An administrator JWT for API/CLI and a separate HttpOnly cookie for the console. Agents use a different JWT issued at registration (auth.agent_token_ttl); do not reuse administrator tokens on Agent hosts.

Failure modes

Scroll sideways to see the full table. Use the arrow keys when focused.

Status / symptomMeaningAction
400 Invalid request formatJSON bind failuresend username/password
400 Secure HTTPS session requiredcookie policy rejected issuanceuse HTTPS; CLI still uses Bearer JWT
401 Invalid credentialsunknown user, bad password, or lookup failuredo not distinguish these in logs that leave the host
401 requires_2faTOTP requiredsend two_factor_code
401 Invalid two-factor authentication codeTOTP/backup failretry with current code
403 Account is inactiveactive == falseanother admin must reactivate
429 / login rejectedLoginRateLimiterwait; do not spray passwords
Manager will not startmissing jwt_secret / invalid JWT materialEnterprise: systemd credential, YAML empty
Default admin missing after installbootstrap password never providedrestore admin_initial_password only through the secret contract

Troubleshooting

  • CLI connection refused on port 8080: set --manager-url to the TLS listener.
  • Console login works, API 401: Authorization header missing or expired; auth.token_expiry example is 8h.
  • Cookie login fails, curl JWT works: cookie policy requires HTTPS.
  • Cannot create users: caller is not admin (viewer JWT is insufficient for /api/v1/users).

Security considerations

  • Do not log passwords, JWTs, TOTP secrets, or backup codes.
  • Do not store session_token in localStorage.
  • Prefer interactive zwallctl login over --password.
  • Enterprise production: keep auth.jwt_secret and auth.default_admin.password empty in YAML. zwallctl secret-rotate on Enterprise defers to scripts/setup.sh --manager-secret-custody rotate (not certified).
  • Community secret-rotate [jwt|db|all] still talks to the Manager API; still do not commit secrets.

Limitations

  • No OIDC/LDAP/SSO.
  • Named roles exist; most mutating operator APIs require admin.
  • Change-password JSON field names are not published as a DTO in this package.
  • Secret-custody installer is implemented pending final security acceptance.