Configure Local Accounts
Local accounts are the self-managed, no-IdP path to a bearer key: a tenant-admin provisions username/password accounts, and a user signs in to mint a short-TTL key scoped to that tenant. Use this when you want authenticated UI/API access without running an OIDC provider. For the trust model behind keys and authorization, see Security Model; for OIDC instead, see Configure OIDC Single Sign-On.
Local accounts are a server-only feature (Postgres-backed). They have no meaning in embedded or daemon mode.
- A running
cloacina-server(see Deploying the API Server). - A tenant-admin key for the tenant you’re provisioning accounts in — either
an
is_adminkey, or a key created withrole=adminfor that tenant. See Manage API Keys. - The tenant must already exist.
The examples use acme as the tenant and $ADMIN as the tenant-admin bearer
key; the server is at http://localhost:8080.
A tenant-admin creates accounts under the tenant’s account surface. role is the
account’s tenant role (read / write / admin) and is baked into every key the
account mints.
curl -X POST http://localhost:8080/v1/tenants/acme/accounts \
-H "Authorization: Bearer $ADMIN" \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "<initial-password>", "role": "write"}'
Accounts are unique per (tenant, username) — the same username may exist
independently in different tenants (this is how one person belongs to several
tenants; see Multi-tenant individuals). Passwords
are hashed with argon2id; the plaintext is never stored or logged.
Login is public (the caller has no key yet). It validates the credentials and returns a freshly minted, tenant-scoped bearer key:
curl -X POST http://localhost:8080/v1/auth/local/login \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "<password>", "tenant": "acme"}'
# => { "key": "clk_…", "tenant_id": "acme", "role": "write", "expires_at": "…" }
Use the returned key as the Authorization: Bearer token for subsequent calls.
A wrong username or password returns 401 with an opaque error (no account
enumeration).
In the web UI, the connect screen’s Username & password tab does exactly
this: it calls /auth/local/login, stores the minted key, and lands the user
authenticated.
All account-management routes are tenant-scoped and require a tenant-admin key.
# List the tenant's accounts
curl -H "Authorization: Bearer $ADMIN" \
http://localhost:8080/v1/tenants/acme/accounts
# Disable an account (its existing keys keep working until they expire/are revoked)
curl -X DELETE -H "Authorization: Bearer $ADMIN" \
http://localhost:8080/v1/tenants/acme/accounts/<account_id>
# Reset an account's password
curl -X POST http://localhost:8080/v1/tenants/acme/accounts/<account_id>/password \
-H "Authorization: Bearer $ADMIN" -H "Content-Type: application/json" \
-d '{"password": "<new-password>"}'
A disabled account can no longer log in; refresh (below) also fails for it, so
its sessions die at their next refresh. The UI exposes the same operations under
Accounts (visible to tenant-admins).
Minted keys carry a short TTL. Re-mint before it lapses rather than storing a long-lived credential:
# Re-check the account is still active, mint a fresh key, revoke the old one
curl -X POST -H "Authorization: Bearer $KEY" http://localhost:8080/v1/auth/refresh
# Revoke the current key + clear the session
curl -X POST -H "Authorization: Bearer $KEY" http://localhost:8080/v1/auth/logout
The web UI runs the refresh loop automatically (silently, before the TTL lapses), so a signed-in operator’s session survives; a pasted long-lived API key is left untouched.
The tenant is the isolation boundary and a key is always scoped to one tenant. A
person who works in several tenants has a separate account in each and holds
one key per tenant; the UI’s tenant switcher flips between them. For example,
alice can be an admin in acme and read in public as two independent
accounts — each key is hard-isolated to its tenant (the other tenant’s data is a
403). See the Security Model
for why the subject stays single-tenant.
- Configure OIDC Single Sign-On — the IdP-backed alternative.
- Manage API Keys — directly-created, long-lived keys.
- Security Model — the ABAC authorization layer and session lifecycle.