TDIF technical profile
Scope: technical profile only; no accreditation claims. Atlantis, its agencies and every person are fictional.
Source of requirements. The current instrument is the Digital ID (AGDIS) Data Standards 2024: Schedule 1 (onboarding), Schedule 2 (AGDIS OpenID Connect Profile, "based off" TDIF 06B OpenID Connect 1.0 Profile release 4.8) and Schedule 3 (attribute profile). Clause numbers below are from Schedules 1 and 2. Schedule 3 wasn't readable from the published HTML; attribute claim names below are the standard OIDC ones the profile reuses. TDIF roles map to the demo as: Identity Service Provider (ISP) = Atlantis ID and Poseidon ID; Identity Exchange (IXP) = the Keycloak exchange; Relying Party (RP) = Agency A. Agency B's wallet flow is outside the OIDC profile and is listed separately.
Status key: ✅ meets · ⚠️ partial · ❌ gap.
1. RP ↔ Exchange (Agency A → Keycloak)
| # | Requirement | Clause | Status | Now | Gap / change |
|---|---|---|---|---|---|
| 1.1 | Authorization code flow, exact redirect URI, state, nonce |
Sch 2 §1.7 | ✅ | All present in oidc-client.ts |
— |
| 1.2 | PKCE with S256; plain refused |
Sch 2 §1.8.1.5 | ✅ | S256 on every request; Keycloak client requires S256 | — |
| 1.3 | Client authentication only private_key_jwt; assertion ≤ 300 s with iss, sub, aud, jti, exp |
Sch 2 §1.8.1.2 | ✅ | Agency A signs ES256 client assertions (3 min) with a persistent key published at /jwks; Keycloak client uses Signed JWT |
Agency A signs a client assertion (code exists in keys.ts) and publishes a JWKS; Keycloak client switches to "Signed JWT" with the JWKS URL. |
| 1.4 | Request objects signed with the client's key; IXP validates | Sch 2 §1.7.5, §1.8.5 | ✅ | Agency A sends a signed request JWT; Keycloak requires and verifies it |
Agency A sends a signed request JWT; Keycloak client set to require request objects and verify with the same JWKS. |
| 1.5 | Request-object encryption to the IXP | Sch 2 §1.7.5 | ✅ | Agency A encrypts its signed request object to the exchange's RSA-OAEP-256 key (A256GCM). A client policy (tdif-agdis-rp: secure-request-object with encryption required and nbf verified, PKCE enforcer, private_key_jwt only) refuses plain requests: "Request object not encrypted" |
|
| 1.6 | ID token signed RS256 (or stronger) | Sch 2 §1.8.3 | ✅ | Keycloak RS256; client allows RS256/ES256/PS256 | — |
| 1.7 | ID token claims: iss, aud, sub (pairwise), acr, jti, exp, iat, nbf, nonce |
Sch 2 §1.8.3.1 | ✅ | All present. nbf comes from the Atlantis extension's mapper (nbf = iat) on ID and access tokens |
|
| 1.8 | ID token ≤ 300 s; access token ≤ 3 600 s | Sch 2 §1.8.3 | ✅ | Both 300 s | — |
| 1.9 | Pairwise sub per RP (OIDC Core §8.1); sector identifier recommended |
Sch 1 §1.2.2, Sch 2 §1.8.3.1 | ✅ | oidc-sha256-pairwise-sub-mapper, one redirect host |
Sector identifier URI: roadmap if Agency A gains more hosts. |
| 1.10 | acr_values or essential acr honoured; achieved level always returned |
Sch 2 §1.9.3, §1.8.6 | ✅ | Changing payment details at Agency A asks for an essential ip1:cl2 in the claims parameter, with prompt=login and max_age=0. The exchange maps TDIF levels to Keycloak LoA, passes max_age upstream, and its post-broker-login gate refuses when the provider's achieved level is lower. Poseidon ID (CL1) is refused; a met level completes |
Verified live with an Atlantis ID passkey: Auth0 asked again, achieved ip1:cl2, step-up completed. |
| 1.11 | Consent collected at the IXP before returning to the RP | Sch 2 §1.10 | ✅ | Keycloak consent screen for Agency A; recorded in the LOGIN event (consent: GRANTED) and shown on the dashboard |
Turn on client consent for Agency A in the exchange realm (scope-level consent screen); consent is then in Keycloak's audit events. |
| 1.12 | RP audit ID generated per RP request, returned to the RP, never sent to the ISP | Sch 1 §2.1.2 | ✅ | rp_audit_id claim from the extension's mapper equals the exchange's code_id on every event of the sign-in; Agency A shows it; it never goes upstream |
An SSO re-login reuses the session's id. |
| 1.13 | Discovery and JWKS, key rotation | Sch 2 §1.8 | ✅ | Keycloak discovery; Agency A reads jwks_uri |
— |
| 1.14 | Single logout if SSO is offered; front-channel supported | Sch 1 §2.1.6–7 | ✅ | OIDC front-channel logout (iss + sid) to Agency A; signing out at the exchange ends the Agency A session, and RP-initiated logout reaches Poseidon ID |
|
| 1.15 | Double-blind: RP must not learn the ISP; ISP must not learn the RP | Sch 1 (exchange purpose) | ✅ | idp claim removed; Agency A's pages and events never name a provider |
Remove the claim and the wording; only the exchange (and the dashboard's exchange lane) knows. |
2. ISP ↔ Exchange (Auth0 / Poseidon → Keycloak)
| # | Requirement | Clause | Status | Now | Gap / change |
|---|---|---|---|---|---|
| 2.1 | Same OIDC profile on the ISP leg: code flow, PKCE, private_key_jwt, signed request objects |
Sch 2 §2 | ✅ Poseidon / ⚠️ Auth0 | Code flow + PKCE S256 and private_key_jwt on both legs. The extension's broker override sends signed request objects; Poseidon ID now requires them. Auth0 accepts signed request objects only on Enterprise with the Highly Regulated Identity add-on, so the Auth0 leg stays unsigned |
Done for Poseidon; Auth0 is a plan limit. |
| 2.2 | IXP verifies iss, aud, nonce, exp, iat, nbf |
Sch 2 §2.6.5 | ✅ | Keycloak broker validation | — |
| 2.3 | ISP returns the achieved acr as a TDIF URN |
Sch 2 §1.8.6 | ✅ | Auth0 Action: the IP level from Atlantis ID's identity verification (app_metadata.tdif_ip: IP1, IP1 Plus or IP2, see section 7) with passkey → CL2, password → CL1 (claim tdif_acr). IP2 has no CL1 level, so a password sign-in to an IP2 identity asserts ip1p:cl1. Poseidon fixed ip1:cl1 |
Atlantis ID: an Auth0 Action sets the level from the sign-in method (passkey → ip1:cl2, password → ip1:cl1). Poseidon: fixed ip1:cl1. The exchange imports it and passes it on. |
| 2.4 | ISP audit identifier in state, distinct from the RP audit ID; ISP logs requests and responses with client_id and state |
Sch 2 §2.6.1, §2.7.1 | ✅ | Keycloak uses its own state per broker login; Auth0 tenant logs and Poseidon events are both collected |
— |
| 2.5 | ISP should issue pairwise identifiers | Sch 1 §1.2.3 | ✅ Poseidon / ◐ Auth0 | Poseidon issues pairwise sub. Auth0 can't change sub, so the Atlantis ID Action adds https://id-atlantis.xyz/pairwise_sub (HMAC of client and user id) and the exchange links accounts by that claim, not Auth0's global id |
Verified live with an Atlantis ID passkey sign-in. Auth0's own sub still appears in the token it sends the exchange; the exchange doesn't call Auth0's userinfo endpoint. |
| 2.6 | ID token lifetime ≤ 300 s | Sch 2 §1.8.3.1 | ✅ | Auth0 apps 300 s; Poseidon 300 s | — |
| 2.7 | Exchange retains no attributes beyond the transaction | Sch 1 (role of the IXP) | ✅ | After the RP's token call the extension's listener removes the brokered user's names, email, username and attributes; a pseudonymous record keeps the provider link, pairwise subject and consent. The next sign-in refreshes attributes for that transaction only | |
| 2.8 | Consent / notice when the IXP remembers the ISP choice | Sch 1 §2.2 | ✅ | The exchange never remembers the choice | — |
3. Assurance levels
| # | Requirement | Status | Change |
|---|---|---|---|
| 3.1 | acr format urn:id.gov.au:tdif:acr:<ip>:<cl>; levels IP1–IP4 × CL1–CL3 (Sch 1 Table 1) |
✅ | Adopt the URN scheme. Demo levels: IP1 (self-asserted), IP1 Plus (passport chip) and IP2 (passport and Medicare card) with CL1 (password) or CL2 (passkey, phishing-resistant multi-factor). The exchange's acr.loa.map holds all 13 levels of Sch 1 Table 1. |
| 3.2 | Level flows ISP → IXP → RP unchanged; RP can request a level | ✅ | Auth0 Action and Poseidon mapper (2.3), Keycloak attribute import + acr protocol mapper replacing Keycloak's own, Agency A shows the level and sends acr_values. |
| 3.3 | Level shown to the citizen and on the dashboard | ✅ | Agency A account page and dashboard event detail. |
4. Consent and attribute minimisation
| # | Requirement | Status | Change |
|---|---|---|---|
| 4.1 | Express consent before attributes reach the RP; consent recorded in audit logs (TDIF 04 PRIV-03-09) | ✅ | Item 1.11. Keycloak records GRANT_CONSENT; the collector already forwards it. |
| 4.2 | Only requested attributes released; standard claim names (given_name, family_name, birthdate, email) |
✅ | Scopes profile email; names match. |
| 4.3 | Citizen can see and revoke consent (user dashboard, Sch 1 §2.3) | ✅ | Agency A links to the exchange's account console, which lists the consent and removes it; the next sign-in asks again. REVOKE_GRANT shows on the dashboard. The console still carries Keycloak's own branding. |
5. Audit
| # | Requirement | Status | Change |
|---|---|---|---|
| 5.1 | Every participant logs authentication requests/responses, consent, attribute release, with timestamps | ✅ | The hub links every event to the one before with HMAC-SHA-256; /events/verify checks the chain and the dashboard shows its status; a reset starts a new chain that records the old head. Retention is still the demo's lifetime. |
| 5.2 | RP audit ID in ASP and exchange logs | ✅ | Item 1.12. |
| 5.3 | Logs never contain credentials or full attribute values | ✅ | Designed in from the start. |
6. Outside the OIDC profile
The wallet credential flow (Atlantis ID → wallet → Agency B over OpenID4VCI/OpenID4VP) is not covered by Schedule 2. It should be presented as a complementary pattern, with its own privacy properties (selective disclosure, no exchange in the path), not as TDIF-conformant.
7. Identity proofing (Digital ID (Accreditation) Rules 2024)
Not part of the OIDC profile above, and not counted in it. Atlantis ID proofs identities with Atlantis Wallet reading an Australian passport over NFC.
| # | Requirement | Rule | Status | How |
|---|---|---|---|---|
| 7.1 | Technical verification of an Australian passport: ICAO Doc 9303 passive authentication with remote PKI | r 5.12(a), Sch 1 item 2, Sch 4 item 1 | ✅ | services/emrtd.ts: data-group hashes against the SOD, the SOD's CMS signature by the Document Signer, the DS certificate chained to one of DFAT's CSCA keys (2013, 2017, 2020, 2024, with link certificates; certs/au-csca). The wallet reads EF.SOD, DG1, DG14 and DG15, never DG2 (the photo) |
| 7.2 | Check DFAT's certificate revocation list | r 5.12(b) | ✅ | Atlantis ID fetches the CRL from passports.gov.au at start-up and every 12 hours, checks DFAT's signature on it, and refuses a revoked Document Signer or an out-of-date list |
| 7.3 | Chip is genuine, not a copy | Doc 9303 Part 11 §6 | ◐ | Active authentication with Atlantis ID's own challenge, checked on the server when the chip has a DG15 key (RSA ISO 9796-2 or ECDSA). Chip authentication (DG14) runs on the phone and is reported, not checked by the server |
| 7.4 | Name and date of birth verified only by source or technical verification (IP1 Plus and above) | r 5.10 item 9 | ✅ | The passport's MRZ replaces the self-asserted name and date of birth; they can't be edited while verified |
| 7.5 | One identity per document | r 5.10 item 2 | ✅ | An HMAC of issuing state and document number binds the passport to one Atlantis ID |
| 7.6 | Passport current, or expired by no more than 3 years | Sch 1 item 2, Sch 4 item 1 | ✅ | Checked from the MRZ |
| 7.7 | IP1 Plus: one photo ID | r 5.10 item 11 | ✅ | The verified passport |
| 7.8 | IP2: a commencement of identity credential and a UitC credential, linking credential if names differ | r 5.10 items 10, 12, 13 | ◐ | Passport as the CoI; a Medicare card as the UitC with the name compared against the passport. The Medicare check is simulated: there is no Document Verification Service connection, so only the number's check digit is tested |
| 7.9 | Fraud and deceased checks | r 5.10 items 3, 6 | ❌ | Not in the demo |