Token Binding (Driver Layer)#
To bind authorization codes and refresh tokens to a DPoP key pair held on the device, configure token binding when constructing HaapiTokenManager. For the concept and security trade-offs, see Token Binding.
Configuration#
let tokenBoundConfiguration = BoundedTokenConfiguration(
keyPairType: CryptoKeyType.secureEnclave
)
let haapiTokenManager = HaapiTokenManagerBuilder(
tokenEndpoint: tokenEndpoint,
clientId: clientId
)
.setTokenBoundConfiguration(config: tokenBoundConfiguration)
.build()Use CryptoKeyType.secureEnclave when available; fall back to CryptoKeyType.p256 (software-backed) when Secure Enclave is not viable — some boot-state scenarios make the Secure Enclave key inaccessible.
val dpopTokenBinding = DpopTokenBindingConfiguration.Builder(context) {
storage = StorageBuilder.encrypted(context) // default; or StorageBuilder.shared(context)
keyType = DpopKeyPolicy.Auto // default; or Hardware / Software
}.build()
val haapiTokenManager = HaapiTokenManager.Builder(
clientId = clientId,
tokenEndpointUri = tokenEndpointUri
)
.setDpopTokenBinding(dpopTokenBinding)
.build()DpopTokenBindingConfiguration.Builder(context) carries the key mode, storage, signing algorithm (ES256 by default), and time source in one object. With only an application Context it defaults to a hardware-backed key and an encrypted store — you no longer implement Storage yourself:
StorageBuilder—encrypted(context)(AES-GCM at rest under a non-exportable Keystore key; the default) orshared(context)(encrypted and readable across the app’s processes/components). Its Keystore key is provisioned on first construction, serialized across the app’s processes with a file lock so constructing the store from several processes at once still yields a single shared key.DpopKeyPolicy—Auto(default; hardware-backed with a transparent software fallback),Hardware(fails closed with a typed error when no usable Keystore is present), orSoftware(encrypted software key).haapiTokenManager.dpopKeyInUse— the key protection actually in use (Hardware,Software, orNone), so anAutofallback is observable rather than silent.
The legacy TokenBoundConfiguration / setTokenBoundConfiguration(...) path remains for backward compatibility but is deprecated in favor of the builder above. setDpopTokenBinding(...) and setTokenBoundConfiguration(...) are mutually exclusive — supplying both is rejected immediately. Switching an established session to the builder needs no re-authentication: a legacy key stored under the manager’s key-store alias is adopted in place automatically, and a custom legacy alias is adopted by setting migrateFromLegacyAlias on the builder.
Using the Bound Key at the Token Endpoint#
When binding is enabled, the OAuth token exchange must include a DPoP proof signed with the bound key. The Driver Layer surfaces the proof generator through haapiTokenManager.dpop (iOS) or haapiTokenManager.dpopHelper (Android), but the token-endpoint exchange itself happens at the SDK Layer through OAuthTokenManager. See OAuthTokenManager for the exchange call.
Omitting the DPoP proof on the token exchange when binding is enabled yields a server error invalid_dpop_proof. The client must always pass the active DPoP key when <issue-token-bound-authorization-code>true</issue-token-bound-authorization-code> is set on the server.
How to implement this: Token Binding (concept) · How to Configure Token Binding · HaapiTokenManager