Upgrade — React Native vN#
Status: the React Native SDK has not reached a major-version transition yet. The next release still carries one breaking change, which fixes token binding that could not be turned off — see token binding off by default. Other notable changes are listed under Current Notes.
What Will Land Here#
When the React Native SDK reaches a major-version boundary that warrants migration guidance, this page will cover:
- Breaking changes — TypeScript signature changes, removed exports, behavioral shifts
- Migration steps — typical changes per consumer (hook-based, imperative)
- Native-bridge compatibility — minimum iOS / Android native SDK versions paired with each RN release
- Backward-compatibility window — which previous APIs survive as deprecated shims
Current Notes#
Next release — token binding off by default#
Action may be needed. Token binding (DPoP-bound authorization codes and refresh tokens) is now off unless you configure it, on both platforms. Before, it could not be turned off: iOS bound tokens even with UnboundedTokenConfiguration, and Android always bound them. Without binding, refresh tokens are no longer sender-constrained — a leaked refresh token is not tied to the device’s key — so keep binding on if you relied on that.
If your client issues token-bound authorization codes (issue-token-bound-authorization-code is enabled), you must opt in, or the token exchange is refused with a token error response.
If you set tokenBoundConfiguration yourself, it keeps working. The setting is deprecated, not removed: createIOSConfiguration and createAndroidConfiguration still accept it and map it onto its replacement, tokenBindingConfiguration. A BoundTokenConfiguration stays bound with the same settings. An Android { keyAlias, keyPairAlgorithmConfiguration, storageOption } object stays bound and keeps using the key at its keyAlias, with the same algorithm and storage. Existing sessions are kept either way. UnboundedTokenConfiguration now really turns binding off. Move to the new names when convenient — the old ones will be removed in a future major version:
| Deprecated | Replacement |
|---|---|
tokenBoundConfiguration: … | tokenBindingConfiguration: … |
new UnboundedTokenConfiguration() | noTokenBinding() — or omit the setting |
new BoundTokenConfiguration(CryptoKeyType.P256) | iOSTokenBinding({ keyPairType: CryptoKeyType.P256 }) |
{ keyAlias, keyPairAlgorithmConfiguration, storageOption } | AndroidTokenBinding({ migrateFromLegacyAlias: keyAlias, keyPairAlgorithmConfiguration, storageOption }) |
IOSTokenBoundConfiguration / AndroidTokenBoundConfiguration types | IOSTokenBindingConfiguration / AndroidTokenBindingConfiguration |
Passing both tokenBoundConfiguration and tokenBindingConfiguration throws, since the SDK can’t tell which one you mean. A configuration object you build yourself, without the factories, that still carries tokenBoundConfiguration keeps binding too; there the SDK can’t tell UnboundedTokenConfiguration apart, so use noTokenBinding() to turn binding off.
If you relied on the default and want to keep your current setup and your users’ sessions, pass the binding factories. On iOS, iOSTokenBinding() uses the same key type earlier versions did. On Android, earlier versions bound tokens to a key at the Keystore alias haapi_token_bound_key; name that alias as the key to adopt, so existing refresh tokens keep refreshing (the algorithm and storage defaults are the ones earlier versions used):
const iosConfig = createIOSConfiguration({
clientId: 'haapi-ios-client',
appRedirect: 'com.example.app://haapi',
tokenBindingConfiguration: iOSTokenBinding(),
})
const androidConfig = createAndroidConfiguration({
clientId: 'haapi-android-client',
appRedirect: 'com.example.app://haapi',
tokenBindingConfiguration: AndroidTokenBinding({ migrateFromLegacyAlias: 'haapi_token_bound_key' }),
})
On Android, adopting the key is opt-in, as in the native SDK: AndroidTokenBinding() with no options binds new sign-ins, but an existing sign-in’s refresh token can no longer be refreshed and the user signs in once more. If your app set its own keyAlias, name that alias instead, and keep the same keyPairAlgorithmConfiguration. If it used StorageOption.Custom, keep it (storageOption: StorageOption.Custom) with the same registered store. Keep the same keyStoreAlias too: new keys are named after it.
New on Android: key policy. AndroidTokenBinding() also takes keyType: DpopKeyPolicy.Auto (the default — a hardware-backed key, with a software key on devices that can’t create one), Hardware (sign-in fails with HaapiErrorCode.DpopKeyCreationFailure on such a device) or Software. It applies to new keys whenever attestation is off — including when the SDK falls back to dynamic client registration on a device that can’t attest, so Hardware can fail sign-in there — and an adopted key keeps the protection it was created with.
If you adopt the new default (no binding) with users already signed in:
- iOS — existing sessions keep refreshing; new ones are unbound.
- Android — refresh tokens issued while bound can’t be refreshed without their proof (
invalid_dpop_proof), so those users sign in once more. Handle it like any failed refresh. Any key material earlier versions left in plaintext storage is moved into encrypted storage on the first init; nothing is deleted.
Invalid configurations fail fast. A tokenBindingConfiguration value that isn’t built by one of the factories, or is missing a field, fails initialization with HaapiErrorCode.InvalidConfiguration. It is never silently treated as “no binding”.
Android error code change. An incomplete client-authentication configuration (a missing field, or a key store option with no registered key store) now fails with HaapiErrorCode.InvalidConfiguration, as on iOS, instead of HaapiErrorCode.Unexpected.
Next release — JWT client authentication on iOS and configuration checks#
No action needed unless you worked around the iOS failure below.
- JWT client authentication works on iOS. A configuration built with
jwtSymmetricAuthentication()oriOSJwtAsymmetricAuthentication()failedinitializeForHaapi/initializeForOAuthon iOS. It now initializes, and the flow, token requests and revocation authenticate with the signed JWT. Android was not affected. - If you worked around it with lowercase algorithm names (for example
'hs256' as JwtSymmetricAlgorithm), use the enum members instead —JwtSymmetricAlgorithm.HS256,JwtAsymmetricAlgorithm.ES256— since lowercase names are now rejected on both platforms. - New error for unsupported configuration values. A value outside its enum (an algorithm, a
kind, an option) now failsinitializeForHaapi/initializeForOAuthwithHaapiErrorCode.InvalidConfiguration(categoryconfiguration) on both platforms, before any native work. Before, the call failed with an untyped error whose code differed per platform. Both platforms’ configurations are checked whichever platform the app runs on, and the error description names the field and its allowed values. getRNLoggercan throw. The call that constructs the logger now throwsHaapiErrorCode.InvalidConfigurationsynchronously whenlogLevelis not anRNLogLevelvalue; nothing is cached, so a later call with a valid configuration works.
Next release — native SDKs iOS 5.7.1 and Android 5.7.0#
The bundled native SDKs move to iOS 5.7.1 and Android 5.7.0. The React Native API is unchanged. Two fixes need no action: on iOS, token revocation now uses the configured client authentication, so clients that authenticate with a secret, a signed JWT or mutual TLS no longer fail revocation with HTTP 401; on Android, a token-bound sign-in keeps refreshing after later sign-ins, app restarts and HAAPI access-token expiry.
- Links to another host are refused. On both platforms,
followLinknow refuses a link whose target is not the configured identity server, without sending the request, and rejects withHaapiErrorCode.Unexpected(categoryunexpected). Make surebaseUrlis the host that serves your HAAPI representations — behind a reverse proxy, the proxy’s public host — and open links meant for the browser (amailto:link, a document) with the platform’sLinkingAPI rather thanfollowLink. - Revoke the refresh token when a sign-in is no longer needed. On Android, each token-bound sign-in now keeps its own DPoP key until its refresh token is revoked; discarding the tokens leaves the key on the device. Call
oauthTokenManager.revokeRefreshTokenwhen the user signs out of your app. Closing an accessor does not revoke anything. - iOS DPoP proofs for an access token always carry
ath. A proof created for an access token now always includes the access-token hash claim, as RFC 9449 requires; previously it was included only together with a DPoP nonce. Servers configured withuse-legacy-dpopseeathon API-call proofs that previously omitted it. - Building for iOS requires Xcode 27 or later. The bundled iOS SDK’s own minimum is now iOS 15.0; the React Native package’s minimum stays iOS 16.4, so no deployment-target change is needed.
iOS 27 and the scene lifecycle. iOS 27 refuses to launch an app built with the iOS 27 SDK unless the app adopts the UIScene lifecycle. This is a platform rule rather than an SDK change, but apps generated from the Expo SDK 57 template have not adopted it yet: declare a UIApplicationSceneManifest in Info.plist with a scene delegate that creates the window and starts React Native.
Next release — encrypted default Android storage#
No action needed. On Android, StorageOption.Default now keeps token-binding values — including the software-fallback DPoP key pair — encrypted at rest under a non-exportable Android Keystore key. Values stored by earlier versions move into the encrypted store automatically on the first init after the upgrade, so users stay signed in. Stored values are bound to the device; see Native Resolvers → Default storage and migration for failure handling and backup recommendations. Apps using StorageOption.Custom are unaffected.
- New error:
HaapiErrorCode.StorageUnavailable(retryable). On Android,initializeForHaapi/initializeForOAuthcan reject with it when the Keystore is temporarily unavailable after the values have moved: retry once the app is back in the foreground to keep the existing sign-in. After three consecutive failed attempts the SDK starts without the stored key, so the app is never blocked. - iOS error code change. An error caused by a locked or unusable keychain (typically a DPoP failure) is now reported as
HaapiErrorCode.StorageUnavailablewith categoryhaapi, instead of aHAAPI_DPOP_*code with categorydpop. The recovery (retry when the app is in the foreground) is unchanged; apps that branch on the DPoP codes, or fall back on thedpopcategory, for this case should add the new code or category.
Inherited from the native SDKs#
The relevant upgrade notes for HAAPI integrations on React Native are:
- DPoP Nonce Auto-Management — the RN SDK inherits the auto-managed nonce behavior from the underlying iOS and Android Driver Layers.
- HaapiLogger setLogType Migration — on React Native the log level is set with
getRNLogger({ logLevel, isSensitiveValueMasked })rather thansetLogType/setLevel.
Track via: React Native SDK · Quickstart — React Native