Authentication & Users¶
Enable and operate the four authentication schemes OpenCCU-Loom ships with — Basic, API tokens, browser sessions, and OIDC — plus how roles, the first-admin bootstrap, and CSRF behave.
Who this page is for
Administrators wiring up access control for the daemon's REST API and web UI. For the higher-level posture and threat model, read the security guide.
Roles¶
Authorization is coarse-grained and strictly nested:
| Role | Can do |
|---|---|
viewer | Read-only access to everything its endpoints expose. |
operator | Everything a viewer can, plus mutations: paramset writes, value writes, links, schedules, sysvar writes. |
admin | Everything an operator can, plus dangerous operations: delete device, install mode, backups, config edits, user/token management, diagnostics. |
admin covers operator, which covers viewer. There is no finer per-endpoint permission model.
When role middleware is not wired
If a deployment does not wire the operator/admin role gates, every authenticated user passes — the daemon falls back to "any logged-in user is allowed" so an upgrade never locks anyone out. Wire the role gates to actually enforce the table above.
The first admin (/setup)¶
A fresh daemon with no users defined cannot be administered until you create the first account. The bootstrap UI exposes a single-shot setup flow:
- Browse to the UI port (default
:8081) and open/setup. - Submit a username, password, and password confirmation.
- The account is created with the
adminrole and you are redirected to/login.
Setup runs exactly once
POST /setup refuses to run as soon as any user exists. It always creates an admin. After the first admin is in place, manage further users through the admin API or your configured user store — not through /setup.
Basic auth¶
Username/password over HTTP Basic, also backing the HTML login form. Passwords are compared in constant time.
north:
rest:
auth:
basic_enabled: true
users:
alice: "s3cr3t-password" # username -> password
bob: "another-password"
The users map is a secret-classed config field, so its values are encrypted at rest (see the security guide). Clients send Authorization: Basic <base64(user:pass)>; the realm advertised on a 401 is openccu-loom.
Browser sessions¶
A successful login (form POST /login, or the SPA's POST /api/v1/auth/login) issues a server-side session and drops the session cookie:
- Cookie name:
openccu_loom_session,HttpOnly,SameSite=Lax. - Lifetime: 12 hours (server-side TTL; expired sessions are evicted on read).
- Logout (
POST /logoutorPOST /api/v1/auth/logout) revokes the session and clears the cookie. - The
Secureflag is set when the daemon is told it sits behind TLS — pair this withnorth.rest.csrf_secure: truebehind an HTTPS proxy.
Sessions are in-memory
The default session store does not survive a daemon restart; users re-authenticate after a restart.
API tokens (Bearer)¶
Tokens authenticate non-browser clients (CI, scripts, automation) via Authorization: Bearer <token>. They are compared in constant time and bypass CSRF (a bearer header is never auto-attached by a browser).
Managing tokens over REST¶
All token-management endpoints are admin-only and live under /api/v1/auth:
| Method & path | Purpose |
|---|---|
GET /api/v1/auth/tokens | List tokens (ID, fingerprint, subject, role — never the secret). |
POST /api/v1/auth/tokens | Mint a new token. |
DELETE /api/v1/auth/tokens/{id} | Revoke a token by its stable ID. |
GET /api/v1/auth/users | List configured usernames + roles (admin-only). |
Create a token by posting a subject and role:
curl -u admin:… -X POST https://loom.example/api/v1/auth/tokens \
-H 'Content-Type: application/json' \
-d '{"subject":"ci-runner","role":"operator"}'
The response carries the raw token once:
{
"id": "9f2b1c0a4d5e6f70",
"token": "rX3…urlsafe-base64…",
"fingerprint": "…abc123",
"subject": "ci-runner",
"role": "operator"
}
Store the token immediately
The raw token is returned only at creation. Subsequent list and audit views show only the ID and a six-character fingerprint — the daemon cannot reissue the secret. The token is 32 random bytes as URL-safe base64 (~43 characters). Its ID is the first 16 hex characters of its SHA-256, used as the {id} path segment for revocation.
Valid roles are viewer, operator, admin; anything else is rejected with 422.
OIDC (single sign-on)¶
OIDC uses the authorization-code flow with PKCE (S256). The browser is redirected to your identity provider; on return the daemon issues the same session cookie as a Basic login.
north:
rest:
auth:
oidc:
enabled: true
issuer: "https://idp.example.com/realms/home"
client_id: "openccu-loom"
client_secret: "…" # optional for public clients
redirect_url: "https://loom.example/api/v1/auth/oidc/callback"
role_claim: "role" # defaults to "role" when empty
Step by step:
- Register OpenCCU-Loom as a confidential (or public) client at your IdP. Set its redirect URI to the daemon's
…/auth/oidc/callbackURL, reachable through your reverse proxy. - Fill the
oidcblock above. The daemon discovers the authorization, token, and JWKS endpoints from<issuer>/.well-known/openid-configurationat startup. Default scopes areopenid profile email. - The SPA login page offers a "Login with OIDC" entry that drives
GET /api/v1/auth/oidc/start→ IdP →…/auth/oidc/callback. - Role mapping reads the
role_claimclaim:admin/administrator→admin,operator→operator, everything else →viewer. The session subject ispreferred_usernamewhen present, otherwise thesubclaim.
How the ID token is validated
The callback verifies the ID token's RS256 signature against the provider's JWKS (discovered from jwks_uri and cached), and checks that the issuer matches, the audience contains your client_id, and the token has not expired. PKCE protects the code exchange. A provider that advertises no jwks_uri cannot be verified, so logins against it are refused. See OIDC signature verification for details.
CSRF for browser vs API clients¶
north.rest.csrf_enabled (default true) installs a double-submit guard on mutating requests:
- Browser/SPA (session cookie): the daemon sets a JS-readable
openccu_loom_csrfcookie; the client must echo it back in theX-CSRF-Tokenheader (or_csrfform field) on everyPOST/PUT/PATCH/DELETE. A missing or mismatched token returns403. - API clients (Basic / Bearer): exempt. These schemes carry a per-request
Authorizationheader that browsers never auto-include cross-origin, so no CSRF token is needed.curland automation work unchanged. - Safe methods (
GET/HEAD/OPTIONS): always pass through.
Set north.rest.csrf_secure: true when serving over HTTPS so the CSRF cookie carries the Secure flag.
Related¶
- Security guide — threat model, secrets at rest, TLS.
- Configuration reference — the full
north.rest.authschema lives in the admin configuration guide (docs/admin/configuration.md).