Skip to content
ADR-0060Architecture decisionAcceptedversion 1.0.022 October 2026

Context ​

  • Standard. RFC 9449 §5: the refresh token of an authenticated client is not bound to the DPoP key but to the client; the DPoP key may change on refresh. OAuth 2.0 Attestation-Based Client Authentication (draft -07) §10.3: for a client authenticated with a client attestation, the authorisation server binds the refresh token to the client instance key (the attestation's cnf); a refresh with another instance key is refused.
  • Until now. In Tamga the refresh token was bound to the DPoP key used at issuance (ADR-0023, ADR-0044 K3); the WIA is obtained with a new key for every operation (ADR-0025 K6), which is why the binding was put on the DPoP key.
  • OpenID conformance self-test (2026-10-21, finding G12). The FAPI 2.0 refresh-token module refreshes with a new DPoP key and the same client attestation (must succeed), then with an attestation for another instance key and the old DPoP key (must be refused). Our binding gave the opposite result in both.

Decision ​

K1 — Binding to the client instance key ​

The refresh token is bound to the RFC 7638 thumbprint of the cnf.jwk of the client attestation (WIA) of the operation that issued it. The WIA presented on refresh must carry the same cnf key; otherwise invalid_grant. A DPoP proof is still required (the key may change); the new access token is bound to the new DPoP key.

K2 — The wallet keeps the instance key with the credential ​

The WIA is still obtained with a new key for every operation (ADR-0025 K6). The WIA key used in the operation that returned a refresh token is written into the credential's refresh binding (instanceKeyRef) and not deleted; on refresh the wallet asks the provider for a fresh (< 24 h) WIA for that key. The key is shown only to the institution that issued the credential; other institutions see other keys (unlinkability is kept). When the credential is deleted the key is deleted too (unless another binding or the wallet's current WIA uses it).

K3 — Scope ​

The institution issuer's refresh token (ADR-0023) and the identity service's ZK copy token (ADR-0044 K3). Old (DPoP-bound) tokens stop working (development phase, ADR-0029); the wallet gets a new one with the next issuance.

Options considered ​

  • Keep the DPoP binding. Not required by the standard; two suite checks keep failing.
  • DPoP or instance key (either). Does not meet ATCA §10.3's "refuse another instance key".
  • One persistent instance key for the wallet. Every institution would see the same key — against ADR-0025 K6 (linkability).
  • Chosen: an instance key per credential — the standard binding plus unlinkability across institutions.

Invariants ​

CodeRule
RT1The refresh token is bound to the client instance key (thumbprint of the WIA cnf at issuance); a refresh needs a WIA for the same key.
RT2A DPoP proof is required on refresh; the new access token is bound to that request's DPoP key.
RT3The wallet uses a refresh binding's instance key only with the institution that issued that credential; other operations use a new WIA key.

Implementation ​

  • @tamga-network/issuer 0.7.2: clientInstanceJkt. wallet-core 0.7.2: RefreshBinding.instanceKeyRef, ZkCopyBinding.instanceKeyRef, wuaForBinding, autoRefreshCredential / refreshZkCopies wuaForInstance, instanceKeysInUse; removeCredential also deletes the binding's key.
  • Platform: institution issuer refresh_tokens.instance_jkt (schema step 11) and the instance-key check on refresh; identity service ijk in the ZK token.
  • Wallet (Tamga Wallet): provider protocol requestWia({ keyRef }) (WIA for an existing key); bound keys are not deleted when the WIA changes.
  • Specification: SPEC-PROTO-0001 §4.1–4.2.

Status ​

Accepted — 2026-10-22. Approved by project management (OpenID conformance self-test finding G12). DECISIONS: D-PROTO-4.