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 c09c081appendAuditEvent 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.