Health New Zealand | Te Whatu Ora
Radiology Tools Architecture and handover

Platform

Authentication and security

One component, the identity hub, handles sign-in for the whole platform. People prove who they are with a one-time code sent to their email; the hub then gives each app a short-lived signed token. The apps never see a code, a session or an encryption key.

Design principles

  • Credentials live in one place. Sign-in codes, sessions and the keys that protect personal data exist only in the hub.
  • Apps trust a signature, not a database. Each app holds one verification key and checks the token's signature, expiry and audience. No app queries the hub on every request.
  • Every app gets its own key. The hub signs Site Profiles' tokens with a different key from Triage's, and each token names the app it is for, so a token for one app is refused by another.
  • Personal data is encrypted, and found without decrypting. Names and email addresses are encrypted at rest. Lookups use a keyed hash of the address (a blind index), so finding a person never requires decrypting the table.
  • Environments cannot read each other. Production and preview use different keys for everything.

How a person signs in

Browser Appe.g. Site Profiles Identity hub Hub D1 Email worker 1 open app, no token 2 redirect to hub, with app, return URL, state 3 sign-in page; return URL checked against allowlist 4 email address 5 find by blind index; store HMAC of new code 6 send 6-digit code (service binding) code arrives in the person's inbox; valid 10 minutes 7 code (5 attempts per code) 8 consume code; create session, stored only as a hash 9 set session cookie (HttpOnly, Secure, host-only); redirect to app with 15-minute token for that app 10 token and state check state, signature, expiry and audience LATER, WHEN THE 15-MINUTE TOKEN EXPIRES 11 app sends person back to hub cookie session still valid: new token issued, no code needed
The code, the session and the keys never leave the hub. The app receives only a signed token: the person's opaque ID (not their address), their access, and the app it is for. Sessions last 24 hours, or 30 days if the person ticks "remember me"; owners and Site Profiles or Service Sizing admins always get 30 days, and there is no idle timeout (R23).

Tokens and sessions

Hub sessionApp token
What it isA 32-byte random value in a cookie on the hub's hostname.A JSON Web Token signed with HMAC-SHA256 using that app's own key.
Stored asSHA-256 hash in hub D1, with expiry, user agent and IP.Not stored by the hub. Held by the app in the browser.
Lifetime24 hours, or 30 days with "remember me". Owners and Site Profiles or Service Sizing admins always get 30 days. No idle timeout.15 minutes. When it expires, the next API call fails and the app sends the person through the hub, which returns a new token without a code while the session lasts. The failed request is not retried.
CarriesNothing readable. The cookie is HttpOnly, but the hub's own sign-in and admin pages also receive the raw value in script (R7).Opaque person ID, the keyed blind index of their primary address, display name, owner flag, their tier in every app, all their grants and regions, the role for this app, issuer and audience.
Revoked bySign-out, or deactivating the person (deletes all their sessions).Expiry. An issued token stays valid for up to 15 minutes.

Controls on the sign-in path

ControlDetail
One-time codeSix digits from a cryptographic random source, valid 10 minutes, five attempts, then locked. Only a keyed hash (HMAC) of the code is stored.
Rate limitsFive codes per address per 10 minutes; ten per IP address per hour.
No account discoveryThe hub gives the same answer for known and unknown addresses, and adds a short delay to the unknown case. Timing differences are narrowed, not removed.
Return-address allowlistThe hub redirects only to an exact list of the platform's own hostnames, so it cannot be used to send a token elsewhere.
State checkThe app creates a random state value before redirecting and accepts a token only if the same value comes back, which blocks login cross-site request forgery. Site Profiles' older portal handoff does not use this yet (R7).
Cross-origin rulesThe hub answers browser requests only from an exact list of the platform's origins.
Admin APIAdmin calls need the session value as a bearer header, not the cookie, so another site cannot make them on an admin's behalf.
Sign-in pageServed with a Content-Security-Policy that blocks all external sources (inline script is still allowed, R19) and no referrer.

Data classification and protection

No patient information in the applications' databases

None of the five application databases holds information about patients. They hold staff directory information, facility and equipment data, workforce figures, and generic clinical guidance. Two exceptions sit outside the databases and are in the risk register: historical referral extracts in the repository (R1) and free text that users can paste into Triage's AI search (R5).

ClassExamplesProtection
Staff personal dataNames and email addresses in the hub and in the radiologist, MRT and member registry.Encrypted with AES-256-GCM, a fresh random IV per value. Looked up by HMAC-SHA256 blind index. In the hub, a key that is not exactly 64 hex characters is refused rather than silently used. The older staff user tables in Site Profiles and Triage still hold names and addresses in plain form until they are dropped at the end of the cutover (R14).
Staff profile dataRegion, specialties, leadership roles, service area, directory visibility, consents.Stored in plain form by design, because it is not identifying without the name, and access to it is controlled.
Security recordsSession hashes, code hashes, audit events with IP addresses.Hashes only for secrets. IP addresses kept in plain form for audit.
Facility dataSites, equipment, staffing FTE, contacts, performance.Plain form. Facility contact names and numbers should be encrypted (R14).
Workforce modellingReporting volumes, meetings, required and effective FTE by planning unit.Plain form. Aggregates, not personal.
Clinical guidanceTriage scenarios, modalities, priorities.Plain form. Public reference content.

Why a blind index

To sign someone in, the hub has to find their record from the address they type, but it should not be able to list everyone's address without a reason. So the hub stores two things per address: the address encrypted, and a keyed hash of the normalised address. Sign-in hashes what was typed and looks up the hash. The address itself is decrypted only at the moment an email is sent or an administrator opens the record. Each person can have several verified addresses, each with its own hash, which matters because most staff will sign in to Entra with a different address from the one they use today.

Where each app stands

AppSign-inNotes
Site ProfilesHubVerifies hub tokens for its own audience. Its older password sign-in, and the portal's admin link, still issue legacy tokens from its own user table until the cutover retires them (R7, R10).
Service SizingHubHas never had its own sign-in. Verifies hub tokens for its own audience.
TriageHubReading is public. Editing needs a hub token. Its own password sign-in and a legacy shared editor credential remain until the cutover removes them (R10).
My HNZ RadiologyOwn sessionUses the same email-code pattern, but checks members against the Site Profiles registry and keeps its own session, because hnzradiology.nz cannot share a cookie with hnzradtools.nz. Moving it onto the hub is on the roadmap (R9).
Identity hub adminHubAdmin screen at id.hnzradtools.nz/admin.

Known gaps in this area

These are tracked with fixes in the risk register. In summary:

  • The token is handed to the app in the redirect URL and kept in browser storage. A single-use exchange code and a stricter Content-Security-Policy would close this (R7).
  • For a short window after a successful sign-in, the code-check path is not rate-limited (R11).
  • Any app's administrator can see the whole directory and audit log (R12).
  • Token signing and hashing code is written in-house on Web Crypto, and older copies remain in three apps (R18).
  • Owners and some admins always get 30-day sessions, with no idle timeout (R23).

Entra ID: where it plugs in

Moving sign-in from email codes to Health NZ's Microsoft Entra ID is the natural first win after handover. The architecture already has one place where it belongs, so the change is contained to the hub.

INSIDE THE IDENTITY HUB Email code today Entra ID sign-in added match address by blind index match Entra object ID, or any verified address Same identity same session Same token Four apps Site Profiles, Sizing, Triage, portal no change
Entra becomes a second way to reach the same identity. Everything after "same identity" is shared, so the applications see an identical token from the same issuer. Email codes can stay available for the few people without an Entra account.

Three things already in place make this small:

  • The identity table reserves a unique Entra object ID for each person.
  • Each person can hold several verified addresses. Only about a third of current users sign in with a tewhatuora.govt.nz address; the rest use legacy district addresses, so matching on one address alone would miss most people.
  • Access tiers map directly onto Entra groups (one group per app per tier) if Health NZ later wants group-driven access.

Separately, Cloudflare Access can put Entra sign-in in front of the admin screens as an extra layer, with no application change.