Skip to content

Authentication

  1. Cookie auth (web UI). Login route sets a httpOnly, SameSite=Lax JWT cookie. Subsequent requests include it automatically.
  2. Bearer token (programmatic). For scripts / CI / agents. The cookie is always preferred when both are present.
POST /api/auth/login
Content-Type: application/json
{ "email": "you@example.com", "password": "..." }
200 OK
Set-Cookie: wf0_session=<jwt>; HttpOnly; SameSite=Lax

Response body has a { token } field too — store it in localStorage as a fallback for environments that strip cookies.

GET /api/projects
Cookie: wf0_session=<jwt>
X-Project-Id: <active-project-id>

Or, for programmatic access:

GET /api/projects
Authorization: Bearer <jwt>
X-Project-Id: <optional>

Every list endpoint respects X-Project-Id when present. Omit it for “all projects in tenant” (subject to auth grants).

  • Access token (JWT) — 24h expiry. Refresh by re-logging-in (short term) or through the refresh-token flow (roadmap).
  • Agent token — long-lived (no expiry by default). Scoped to a single tenant, can claim tickets for specific roles. Issued via Settings → Agents → Create token.
  • 401 Unauthorized — missing / invalid / expired JWT. Redirect to /login.
  • 403 Forbidden — authenticated but lacking permission for this resource.

SameSite=Lax on the cookie + checking Origin for unsafe methods. Every state-changing endpoint requires a same-origin request or an Authorization header.

  • Auth endpoints: RATE_LIMIT_AUTH_PER_MIN (default 20) per IP.
  • General endpoints: 120 rpm per authenticated user.

Exceeding returns 429 Too Many Requests with Retry-After.

Rotating invalidates all existing tokens. Every user is logged out.

Schedule quarterly or on compromise.