ADR-0007 Authentication and Authorization V1
Status
Section titled “Status”Accepted on 2026-08-03.
Context
Section titled “Context”The platform needs an interactive V1 login without binding the user and authorization model to the eventual OIDC provider. Tenant and organization grants can change while an access token is still valid, so authorization data inside a long-lived self-contained token would delay revocation and create an unsafe source of truth.
Decision
Section titled “Decision”Fastify remains the platform-api HTTP framework. V1 implements local
username-or-email and password authentication. The local authenticator is the
first implementation of the interactive authentication boundary; OIDC and
tenant-specific Microsoft Entra ID integrations remain later additions.
Passwords use Argon2id PHC strings with 19 MiB memory, time cost 2, parallelism 1, and a 32-byte hash. A local zxcvbn common-password check enforces a score of at least 3 without a network dependency.
Access tokens are HS256 JWTs with a deployment-specific key. They contain only
iss, aud, sub, sid, jti, iat, exp, auth_time, and amr. They
expire after 1,800 seconds. They never contain username, email, tenant IDs,
organization IDs, roles, or grants.
Refresh tokens are opaque random values with 256 bits of entropy. Only their
SHA-256 hashes are stored. Browsers receive them exclusively through a
host-only HttpOnly, SameSite=Strict cookie. Test and production use
__Host-sens_refresh with Secure; local plain HTTP uses sens_refresh.
Refresh rotation extends a seven-day inactivity deadline and has no separate
absolute session deadline.
Every protected request validates the JWT and then loads the current session,
user state, local credential state, platform roles, and membership grants from
PostgreSQL. The database, not JWT claims, is authoritative for authorization.
The API accepts no trusted X-Tenant-ID header; tenant-owned entity paths carry
the tenant ID explicitly.
Refresh and cookie-authenticated logout requests require an exact allowed
Origin. Credentials-enabled CORS never uses a wildcard. Access and refresh
tokens must not be written to local storage, session storage, URLs, logs, or
audit metadata.
Authorization semantics
Section titled “Authorization semantics”platform_admin is a global role. Tenant memberships contain only
tenant_admin, org_admin, and viewer. Multiple grants form a union.
Organization grants can be exact or recursively include descendants. Ancestors
may be exposed only for navigation and never grant access to ancestor or sibling
devices. Unassigned devices are visible only to platform and tenant
administrators. Cross-tenant and out-of-scope resource IDs use not-found
semantics.
Operational consequences
Section titled “Operational consequences”- PostgreSQL availability is required for login, refresh, and every protected API request; readiness reports database failure.
- Login throttling and session limits are database-backed and safe across API replicas.
- A user has at most ten active sessions. The least recently used session is revoked during the eleventh login under a user-row lock.
- Authentication metrics have only bounded event, service, and environment labels; they contain no user or tenant identity.
- Authentication and security events are audited without raw login identifiers, IP addresses, user-agent values, passwords, or tokens.
- Audit records are retained for seven calendar years. A bounded CLI command performs maintenance until a scheduled Kubernetes execution is introduced.
Rejected alternatives
Section titled “Rejected alternatives”- Roles and tenant grants inside access JWTs were rejected because changes and revocations would not be immediate.
- Refresh JWTs were rejected because opaque, hashed, rotating values minimize stored credential usefulness and support replay detection.
- Browser token storage in
localStorageorsessionStoragewas rejected due to script-accessible credential exposure. - An unauthenticated bootstrap HTTP endpoint and an automatically enabled Helm bootstrap Job were rejected. Recovery is an explicit interactive CLI operation controlled by the production owner.
Rollout and rollback
Section titled “Rollout and rollback”The auth migration is additive for the previous API image. Runtime database and auth secrets must exist before deploying the new image. Rollback selects the previous API image and leaves auth tables and credentials intact. Production does not use a down migration or credential deletion as rollback; schema defects are corrected forward.