Skip to content
SENS Platform Docs

ADR-0007 Authentication and Authorization V1

Accepted on 2026-08-03.

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.

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.

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.

  • 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.
  • 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 localStorage or sessionStorage was 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.

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.