Files
kandev/docs/public/authentication.md
T

117 lines
11 KiB
Markdown

---
title: "Authentication & Users"
description: "Enable opt-in authentication, manage users and invites, and use personal access tokens on a shared Kandev server."
status: experimental
---
# Authentication & Users
Kandev ships as a single-user local tool with authentication **disabled**: nothing changes for laptop installs. When several people share one Kandev server, enable authentication to give each person their own account and their own private workspaces.
Authentication is an **experimental runtime feature toggle** and is disabled in every shipped profile. It uses the same system as the other feature flags, so there is no separate "Authentication" configuration page.
## Quick checklist
1. Keep authentication off only for a single-user laptop bound to loopback or another private listener.
2. For any server reachable by other users or networks, enable **Authentication & users** and restart.
3. Create the first admin immediately after setup mode appears.
4. Use personal access tokens for scripts and external MCP clients.
5. Keep TLS, filesystem permissions, and agent credentials as separate security boundaries.
## What changes when authentication is on
- Everyone signs in with email + password. Browser sessions last 30 days (sliding) and can be revoked from `Settings > Account`. The signed-in user is shown in the bottom-left of the sidebar, with a log-out menu.
- **Workspaces become per-user by default.** You only see workspaces you own, including their tasks, sessions, repositories, terminals, previews, and live updates. Existing data is assigned to the admin created during setup. A workspace can then be shared with colleagues on purpose: see [Team Access](team-access.md).
- Secrets are per-user. A **Global** secret is user-global across that user's workspaces; a **Workspace** secret belongs to one of their workspaces. With authentication disabled, Global is install-global. Executors and agent profiles remain shared across the instance, so they can reference Global secrets only; repositories may bind Global or same-workspace secrets.
- With Organizations off, admins manage users and instance settings but do **not** see other users' private workspaces. With Organizations on, org admins manage organization-scoped resources and the separate instance operator manages organizations; neither role grants workspace visibility. The experimental multi-org path still has tenant-boundary gaps in user and system administration; see [Limitations](#limitations).
- Programmatic clients (external MCP, scripts) authenticate with personal access tokens.
## Sharing work with a team
Authentication makes each person's work private. To give a team a shared board without a shared login, see [Team Access](team-access.md): workspace visibility, member roles, and scoped permissions.
## Enabling authentication
**From the UI:** open `Settings > System > Feature Toggles`, turn on **Authentication & users**, and restart Kandev when prompted (it is a restart-required flag, like the other feature toggles). After the restart the instance is in setup mode: the setup wizard appears and the account you create becomes the admin. All existing workspaces, settings, and secrets carry over to that admin.
**From the environment** (fresh servers, Docker, Kubernetes): set
```bash
KANDEV_FEATURES_AUTH=true
```
The instance boots into setup mode and the **first visitor** creates the admin account. Complete the wizard immediately after deploying. When the flag is forced on by the environment variable, it cannot be turned off from the UI.
To turn authentication **off** again, an admin flips the same toggle off (and restarts), or the environment variable is unset. Ownership data is retained.
A server that listens on non-loopback interfaces with authentication disabled logs a startup warning.
## Users and invites
`Settings > Access Control > Users` (admin only):
- **Invite links**: mint a tokenized URL (`/invite?token=…`) and share it out of band. Optional pinned email, member or admin role, single use, 7-day default expiry. No email server needed.
- **Direct creation**: create an account with a password yourself.
- **Disable / role changes**: disabling a user immediately revokes their sessions and tokens. The last active admin cannot be demoted or disabled.
Roles are `admin`, `member`, and `guest`. With Organizations off, an admin manages users and system settings. With Organizations on, membership roles are associated with an organization and the instance operator tier is separate, but the current Users API does not consistently enforce the organization boundary; see [Limitations](#limitations). A guest reaches only explicitly shared workspaces. The current Users page changes accounts between admin and member; assign the API-only guest role as described in [Team Access](team-access.md#current-limits).
With Organizations off, install-wide data in `Settings > System > Data & Logs` and `Settings > System > Storage` follows the same admin/member split. Members can read the Database and Logs tabs, the backup listing, and the Host and Office retention tabs. Creating, downloading, restoring, and deleting a backup is admin only, because a backup is a copy of the whole database and downloading one would export every user's workspaces. Changing storage settings, adopting a Go cache, and running an analysis, cleanup, or quarantine restore/purge are admin only for the same reason database vacuum, optimize, and reset are: they act on the whole install. With Organizations enabled, org admins currently retain these install-wide capabilities; they are not yet restricted to the instance operator.
## Personal access tokens
`Settings > Account > API Tokens`. Tokens look like `kandev_pat_…`, are shown **once** at creation, and are sent as a bearer header:
```bash
curl -H "Authorization: Bearer kandev_pat_..." https://kandev.example.com/api/v1/workspaces
```
External MCP clients (Claude Code, Cursor connecting to `/mcp`) must be configured with a PAT once authentication is enabled. WebSocket clients that cannot send headers can pass `?token=<PAT>` on the connection URL.
## Endpoints that stay public
`/health` (liveness probes) and `/ready` (readiness probes), the login/setup/invite pages, `GET /api/v1/features`, and self-authenticating webhook receivers (automation webhooks with `X-Webhook-Secret`, office channel HMAC webhooks). Everything else requires a session or token.
Plugin webhooks (`/api/plugins/{id}/webhooks/{key}`) are **not** public by default: a plugin's manifest must explicitly declare `webhooks[].public: true` for that specific webhook to accept anonymous requests. Unflagged webhooks require a session or PAT like any other endpoint. See [Plugin manifest reference](plugins-manifest.md).
## Multiple instances on one host
Browsers match cookies by host and ignore the port. Kandev adds the port to its instance-identity cookie names when the host uses a non-default port:
| Cookie name | Name on a non-default port |
| --- | --- |
| `kandev_session` | `kandev_session_<port>` |
| `kandev-active-workspace` | `kandev-active-workspace_<port>` |
| `office-active-workspace` | `office-active-workspace_<port>` |
Default ports follow the URL scheme. HTTP `:80` and HTTPS `:443` use plain cookie names. HTTP `:443` and HTTPS `:80` keep the port suffix. This keeps sessions and workspace selections separate across instances on different ports.
**Reverse proxies must preserve the browser hostname.** The CORS and WebSocket origin gate compares the browser's `Origin` hostname with the request `Host`. It ignores `X-Forwarded-Host` and ports.
- Preserve the full public `Host`, or rewrite only its port and send the correct `X-Forwarded-Host`.
- Kandev reads that forwarded header only from peers in `KANDEV_TRUSTED_PROXIES`, the same list used for `X-Forwarded-For`.
- Rewriting a non-loopback hostname returns 403 before authentication. A loopback alias such as `localhost` to `127.0.0.1` passes this gate.
**Session-cookie migration.** Old auth-enabled builds conflict with each other via the shared unprefixed `kandev_session`; an upgraded instance ignores the legacy session token and requires one re-login (the new build never reads the unprefixed session cookie, so this holds on rollback and re-upgrade too). Workspace selections keep their validated legacy read fallback, so a pre-upgrade selection survives.
A custom `auth.cookieName` disables automatic port isolation. It must be unique per cookie host. See [configuration](configuration.md#authentication-office-plugins-and-feature-flags).
- An explicit `auth.cookieName: kandev_session` keeps the old shared-name behavior. Remove it to use port isolation.
- A custom name does not change the origin gate, which checks hostnames.
- HTTP `:80` and HTTPS `:443` both use plain names. This mechanism does not isolate those two default-port instances.
## What is isolated
When authentication is on, guarded workspace reads and actions are available only to its owner and users who can reach it through [Team Access](team-access.md). Other users receive "not found" even if they know the ID. This covers workspaces, tasks, workflows, sessions, plans, walkthroughs, terminals, VS Code, port previews, git snapshots, Workspace secrets, repository bindings, **and the workspace's third-party integration settings (GitHub/GitLab/Jira/Linear/Sentry/Azure) and automations**. A user's Global secrets are also private to that user. Admin and operator management authority does not reveal workspaces or secrets they cannot otherwise reach.
Shared across the instance (by design): executors, agent profiles, environments, editors, prompts, and system pages.
## Limitations
- **Multi-org user administration is not fully tenant-scoped.** With Organizations enabled, the current Users API lists accounts across organizations and accepts role or status changes for accounts outside the caller's organization. Treat org-admin access to user management as install-wide until this gap is closed.
- **Multi-org system administration is not operator-exclusive.** Backup, database, and storage mutations, runtime feature-toggle changes, and shared configuration mutations (including saved prompts) currently use org-admin scopes, not the instance-operator tier. An org admin can therefore affect install-wide behavior, including prompt instructions used by other organizations, and can download backups that contain their data.
- **WebSocket notification routing is not a strict access boundary.** Workspace-aware fan-out normally narrows recipients by reach, but the regular path falls back to global delivery if reach and owner resolution both fail. See [WebSocket API](websocket-api.md#emitted-notifications-and-recipients).
- **Filesystem and agent credentials are not isolated.** Worktrees and repositories live under one `~/.kandev` tree owned by the OS user running the backend, and agent CLI logins (`gh auth`, `claude login`, provider API keys) authenticate as that OS user, so all app-users share the same on-disk agent credentials, and anyone with shell access to the server can read all files. Authentication isolates users' kandev *data* at the application layer, not the filesystem or per-user agent auth. For hard isolation of agent credentials, run a separate kandev instance per user (or use OS-level access control / sandboxed executors).
- Local accounts only for now; the account model is ready for OIDC/SSO later.
- Authentication does not replace TLS. Terminate HTTPS in front of Kandev (the session cookie is marked `Secure` when the request arrives over TLS or `X-Forwarded-Proto: https`).