Multi-user Deployment
Out of the box Bowire treats every caller as the same person, and on a laptop that is the right answer. On a shared host two things stop being true: anyone who can reach the URL can drive Bowire at your services, and everybody's recordings, environments and tokens are in one pile.
Both have answers today. This page is how they fit together.
Locking the door
An auth provider decides who may use the workbench at all. It is an extension point next to protocol plugins and UI extensions, so the heavyweight identity dependencies only land in installs that use them:
bowire --url … \
--auth-provider oidc \
--auth-oidc-authority https://login.example.com \
--auth-oidc-client-id bowire \
--auth-oidc-required-claim "groups=bowire-users"
With no provider selected, nothing changes and the endpoints stay open — the laptop-friendly default. With one selected, every Bowire route requires an authenticated caller, and a provider named on the command line that cannot be found is a startup failure rather than a silent fallback to no auth.
Kuestenlogik.Bowire.Auth.Oidc is the first concrete provider: any OIDC-compliant IdP — Entra ID, Okta, Keycloak — without provider-specific code paths.
In embedded mode the host's own auth pipeline wins. Bowire only attaches a scheme when you ask it to.
Giving each person their own state
Authentication answers who is calling. It does not move anything on disk: with a provider configured and nothing else, every signed-in person still shares one ~/.bowire/.
Per-identity storage is a separate switch:
{ "Bowire": { "MultiTenant": { "Enabled": true } } }
It is deliberately not implied by configuring a provider. Plenty of installs put a login in front of a workbench that still has one person behind it, and moving their data because they added OIDC would be a surprise rather than a feature.
Switched on, each identity gets a slot under the storage root:
~/.bowire/
├── environments.json ← the single-user layout, still here
├── collections.json
├── plugins/ ← two tiers, admin-managed and yours
└── users/
├── ada-example.com-4f2a1c07/
│ ├── environments.json
│ ├── collections.json
│ ├── recordings/
│ ├── workspaces/
│ └── .migration.json
└── grace-example.com-9b3e5d10/
The slot name is a readable rendering of the subject plus a fingerprint of it. The readable half means an operator can tell whose directory is whose without a lookup table; the fingerprint is what keeps a.b@example.com and a-b@example.com — which render identically — from sharing a slot and reading each other's secrets.
Which claim identifies a person is sub, then nameidentifier, then oid. Override with Bowire:MultiTenant:SubjectClaim; when you do, there is no fallback, because a fallback would quietly file two identities into one slot.
Details of the layout, the instance segment and the project opt-in are in Where Bowire stores things.
Bringing the existing data with you
The day you turn per-identity storage on, every store starts resolving somewhere new. The first person to sign in sees an empty workbench, and the conclusion they draw is that enabling auth cost them their work.
So Bowire offers to bring it across, once, per identity:
Bowire:MultiTenant:Migration |
What happens |
|---|---|
Prompt (default) |
The person is asked once. The first identity to sign in is not reliably the one the data belongs to — it may be the operator's admin account. |
Auto |
Copied into the first identity that signs in, without asking. |
Skip |
Never offered. For starting clean, or when you have already moved the data yourself. |
What it does, and does not, do:
- Copies. Never moves. The originals stay where they are, so you can switch back to single-user without a second migration, and a migration into the wrong slot is undone by declining it in the right one. Delete the originals when you are satisfied — that timing is yours.
- Brings everything except what is not a person's:
plugins,certs,logs,cache,state,scimandproject.jsonstay behind. The rule is an exclusion list on purpose — a new store's data comes along by default, because forgetting to include something loses data while forgetting to exclude something copies a cache. - Leaves a slot that already holds work alone. Merging two sets of environments produces one set nobody can separate again.
- Records the decision in
.migration.jsoninside the slot: what was copied, from where, when, and whether it was accepted or refused. It sits in the slot rather than in a central log, so deleting an identity deletes its record too.
Taking it back
The decision stays reversible under Settings → Data. Undoing an accepted migration moves that account's slot aside — under users/.undone-…, deleting nothing — and puts the data back on offer, including to whoever it actually belongs to. Undoing a refusal only removes the record; anything done in the slot since stays exactly where it is, and if there is work in there the offer correctly does not come back.
The case this exists for is the ordinary one: the operator's admin identity signs in first, accepts, and the person the data belongs to is then told there is nothing to migrate.
From the host
The same decisions are available without a browser, which is where an operator usually is when flipping an install over:
bowire users list # the slots on disk, and what each decided
bowire users migrate ada@example.com # report only — nothing changes
bowire users migrate ada@example.com --apply
bowire users migrate ada@example.com --decline
bowire users migrate ada@example.com --undo
The subject is named rather than inferred: there is no request here, so there is no caller to be. migrate with no flag lists the files it would move and stops, so "what would this do" can be asked without being the answer.
Slot names are derived from the subject and cannot be read back into one — the mapping is deliberately one-way, so list shows directories rather than people.
Plugins in a multi-user install
Installed plugins are not per-identity state. They resolve in two tiers: a machine-wide directory an administrator manages (%ProgramData%\Bowire\plugins, /var/lib/bowire/plugins) and the running account's own directory on top of it. Uninstalling something from the machine tier is refused and names the elevated command that would work.
Changing which plugins are installed is an administrator's action. Install, update, uninstall and the load/unload lifecycle all require it on an install that has an identity provider; without one — a laptop, an embedded host that never configured identities — nothing is gated, because there is one person and gating them protects nobody. The reason is not tidiness: an installed plugin's assemblies are loaded into the server process and share the host's own types with it, so an install by the least-privileged identity would be code execution as the server, next to every other identity's slot.
Disabling a plugin is process-wide, and the file now says so. Unloading re-runs protocol discovery and swaps the registry every session reads, so it was never a per-person setting. Its list lives at <storage root>/disabled-plugins.json, beside the slots rather than inside one. It used to be written under users/<slug>/, which gave each identity a private copy of a shared decision — the last person to touch it decided for everyone. In single-user mode this is the same path it always was, so nothing moves on a laptop; a multi-tenant install that has old per-identity copies simply stops reading them, and they can be deleted.
Per-identity plugin installs stay open deliberately — see #284. Plugins are assemblies loaded into the host process, so "each person brings their own" would mean one person's code running beside another person's data. Where that is genuinely needed, the answer is a process per identity — one Bowire each, with its own slot and its own plugin overlay, behind a router — not an in-process approximation of isolation.
Letting the directory keep the list
Everything above still leaves one thing manual: who exists. SCIM 2.0 provisioning hands that to your identity provider — Okta, Entra ID or Google Workspace create, update and deprovision identities, and a deactivation is enforced rather than merely recorded: the person is refused at the door and their slot is moved out of reach, reversibly, until the purge window closes.
What is still on the roadmap
- A user chip, scoped state copy and admin impersonation in the workbench — #98. Group membership is already synced by SCIM; these are the surfaces that act on it.
When this matters
- A few developers, a laptop each — single-user is fine; share collections through git-backed workspaces.
- A team behind a shared host — an auth provider gates access. Whether you also want per-identity storage depends on whether the state is shared work or personal work.
- Org-wide — per-identity storage, provisioning, and per-identity plugin policy.
Related
- SCIM provisioning — let the identity provider keep the user list
- Where Bowire stores things — scopes, the project opt-in, instances, and the slot layout
- Sidecar deployment
- Standalone CLI
- Embedded mode — the host owns the auth pipeline; Bowire inherits it