Skip to main content

CC6.3 — Authorisation of access (scope-checked API keys)

Status: Implemented Owner: Agent #38 (Senior Compliance Lead, SOC 2 + ISO 27001) Last reviewed: 2026-05-28 Next review: 2026-08-28

Trust Services Criteria reference​

The entity authorises, modifies, or removes access to data, software, functions, and other protected information assets based on roles, responsibilities, or the system design and changes. The control covers role / scope definitions, the enforcement of scope at the access-control point, the change procedure for scope grants, and the audit trail of authorisation decisions.

How ZeroAuth meets this control​

Authorisation is scope-based at the API-key layer. Each tenant API key carries a set of scope literals defined in src/types/. The scopes (sampled from the type definitions) cover the public capabilities: device.register, user.enroll, verification.submit, attendance.submit, audit.read, proof-pairing.create, proof-pairing.submit, etc. Each /v1/* route declares the required scope; the authenticateTenantApiKey middleware in src/middleware/tenant-auth.ts (a) authenticates the key and (b) authorises the request by intersecting key-scope-set with route-required-scope. A request whose key lacks the route's scope returns 403 forbidden_scope.

API-key issuance is mediated by the developer console (/api/console/keys). Console JWTs are required to mint a key; the per-console-user authorisation determines which tenants the user can issue keys for. The key format za_{live,test}_{48 hex} is documented in CLAUDE.md "Load-bearing capabilities" §2. Keys are SHA-256-hashed at rest in api_keys table; the cleartext is shown to the console user exactly once at issue time. Rotation issues a new key and (optionally) revokes the old.

The console + admin authorisation surfaces:

  • Console roles — today, console users have implicit "tenant administrator" scope. Multi-role console (admin / developer / read-only) is on the roadmap as a Phase 1 deliverable; pending Phase 1 ticket allocation.
  • Admin — the single shared x-api-key in .env (/api/admin/*) provides read-only access to stats, blockchain, privacy-audit, leads. No multi-role today; replacement by per-admin keys + rotation is tracked alongside the JWT RS256 migration.

The audit trail of authorisation decisions is the audit_events table. Each key-related write (issued, rotated, revoked, scope-modified) is logged through appendAuditEvent (commit a475ed8); the hash chain (ADR 0013, commit 27ed93c) makes the trail tamper-evident; the on-chain anchor (ADR 0014, commit d6c6a4e) makes it externally verifiable. Direct INSERT INTO audit_events is blocked by tests/audit-chain.test.ts::"every audit-writing surface uses appendAuditEvent" (commit c09c081).

Cross-tenant scope leakage is closed by the source-level guard. tests/tenant-isolation.test.ts (commit a1bbc47) asserts every router.<verb> declaration on /v1/* carries the authenticateTenantApiKey middleware — i.e. there is no "unscoped" route in the tenant API surface by construction. The 14 intentionally-public exceptions are enumerated in PUBLIC_ROUTE_EXCEPTIONS with ≥ 20-character justifications.

Schema-level isolation: tests/schema-purity.test.ts (commit 5425032) locks down the tenant-scoped table columns so a developer cannot quietly add a column that holds cross-tenant data. Every query in src/services/platform.ts (and similar) takes (tenant_id, environment) parameters that flow into the WHERE clause.

Demo-bypass removal: the pairing_demo_mode field on TenantSecurityPolicy was a tenant-scope override allowing demo-prefixed DIDs to skip crypto verification — commit 02e1734 removed the bypass branch from src/services/proof-pairing.ts and marked the field @deprecated. The closure prevents an authorisation override at the tenant-policy layer.

Evidence references​

  • src/middleware/tenant-auth.ts — middleware that combines authentication + scope check.
  • src/types/ — scope literal definitions.
  • CLAUDE.md "Load-bearing capabilities" §2 — API key format documented.
  • Commit a1bbc47 — tenant-isolation source-level guard with PUBLIC_ROUTE_EXCEPTIONS.
  • Commit 5425032 — schema-purity test locking tenant columns.
  • Commit a475ed8 — audit-chain implementation (records key-related events tamper-evidently).
  • Commit d634b2d — /api/admin/audit-integrity endpoint.
  • Commit c09c081 — appendAuditEvent enforcement.
  • Commit 02e1734 — demo-mode bypass closure.
  • ADR 0013-audit-log-hash-chain.md (commit 27ed93c).
  • ADR 0014-on-chain-anchor-cadence.md (commit 27ed93c).

Open gaps + remediation roadmap​

  • Multi-role console (admin / developer / read-only) — Phase 1 deliverable; ticket allocation pending.
  • Per-admin API keys with rotation procedure — pairs with C-11 RS256 migration sprint 2.
  • Quarterly access review — first review target week 26 (2026-11-09) per compliance-roadmap-v1.md D-Q2-12.
  • Console JWT short-lived-access + refresh token — replace today's single long-lived cookie. Target sprint 3.

Test or audit query​

grep -rE "x-zeroauth-required-scope|authenticateTenantApiKey" src/routes/v1/*.ts | wc -l returns the count of scope-gated routes. Cross-check with cat tests/tenant-isolation.test.ts | grep -c "PUBLIC_ROUTE_EXCEPTIONS" — should be 1 (the constant declaration); grep -c "//" docs/threat_model.md | tail checks for residual scope-related notes.