Skip to main content
‹ /compat
Overview

@apexnative/keychain/compat

The functions and constants of react-native-keychain 10 over this package: change the import, keep the calls, and the items get the protection and the errors of the core.

@apexnative/keychain/compat is the API of react-native-keychain 10 with the same names and signatures: setGenericPassword, getInternetCredentials, ACCESS_CONTROL.BIOMETRY_ANY and the rest. Each function is a short call to the core of this package. An app that used rnk changes its import and keeps its code; new code uses the core API.

Usage​

Both forms of rnk's import work:

src/auth/storage.ts
import * as Keychain from '@apexnative/keychain/compat';
// or: import Keychain from '@apexnative/keychain/compat';

await Keychain.setGenericPassword('ivan@example.com', token, {
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});

const credentials = await Keychain.getGenericPassword({
authenticationPrompt: { title: 'Sign in to Example' },
});
if (credentials) signIn(credentials.username, credentials.password);

Named imports work too: import { getGenericPassword, ACCESSIBLE } from '@apexnative/keychain/compat'.

The layer reads and writes this package's items, not rnk's. After the switch, getGenericPassword doesn't see what rnk stored until that data is moved: run migrateFromReactNativeKeychain once, at launch, before the first read. From react-native-keychain walks through both steps.

Where the items go​

There are no items of a separate format: a password from the layer is the credentials of the core, readable by getCredentials.

rnkIn this package
A generic password of the app's default service (no service)The credentials of the default store, keychain: the item credentials, the user name its label
A generic password of the service SThe credentials of the store createKeychain({ service: 'S' })
Internet credentials of the server XThe credentials with server: 'X' in the default store: the item credentials:X
The same with accessGroupThe same, in the store of that access group

The app's default service is the bundle identifier on iOS and the applicationId on Android. Naming it as service is the same as leaving service out.

So the two APIs can share data during a move: an item written by setGenericPassword reads back with keychain.getCredentials(), and the other way round.

import { keychain } from '@apexnative/keychain';
import * as Keychain from '@apexnative/keychain/compat';

await Keychain.setGenericPassword('ivan', 'secret');
await keychain.getCredentials(); // { username: 'ivan', password: 'secret' }
await keychain.getItemInfo('credentials'); // { key: 'credentials', label: 'ivan', … }

The list of services. The package can't list every store on the device, so getAllGenericPasswordServices reads a list of its own: the item generic-services in the store apexnative.keychain.compat. setGenericPassword and migrate add a service to it, and resetGenericPassword removes it. The default service is never in the list, and the default store gets no item of it.

Options and checks​

The options of rnk are mapped to the core's: accessible and accessControl to their constants, cloudSync to synchronizable, accessGroup to the access group of the store, authenticationPrompt to the prompt of the read. Then the core checks them as it checks its own calls:

  • cloudSync: true with an accessControl, or with a …THIS_DEVICE_ONLY accessible, is INVALID_ARGUMENT: iCloud Keychain syncs no such item.
  • An empty user name, a string with U+0000, an empty service or an unknown accessible is INVALID_ARGUMENT.
  • An access control the device can't do fails the write (BIOMETRY_NOT_ENROLLED, PASSCODE_NOT_SET, …). The item is never stored with less protection.

Options that rnk has dropped, or never had (showModal, kLocalizedFallbackTitle, rules, …), are ignored, as rnk ignores them. A read without authenticationPrompt shows rnk's own texts: the title Authenticate to retrieve secret and the button Cancel.

Errors​

Every failure is a KeychainError with a code, where rnk threw plain errors whose message said what happened. A canceled prompt is USER_CANCELED, a new enrollment is KEY_INVALIDATED, and so on: the code is the way to tell them apart.

import * as Keychain from '@apexnative/keychain/compat';
import { KeychainErrorCode, isKeychainError } from '@apexnative/keychain/compat';

export async function unlock() {
try {
return await Keychain.getGenericPassword(); // UserCredentials or false
} catch (error) {
if (isKeychainError(error, KeychainErrorCode.UserCanceled)) return false; // show the password form
throw error;
}
}

KeychainError, KeychainErrorCode and isKeychainError are exported from /compat as well, so the error handling needs no second import. Error codes says what each code means and what to do.

What isn't here​

What rnk takes and this package doesn't do is refused, with UNSUPPORTED and a message that names it, instead of being ignored. Nothing is written in that case.

rnkHere
requestSharedWebCredentials, setSharedWebCredentialsUNSUPPORTED: see below
The storage option, any STORAGE_TYPEUNSUPPORTED: the package chooses its cipher, AES-GCM in the Android Keystore
ACCESSIBLE.ALWAYSUNSUPPORTED: deprecated by iOS; use AFTER_FIRST_UNLOCK
ACCESS_CONTROL.APPLICATION_PASSWORDUNSUPPORTED, on writes and on reads: use a biometry, the passcode, or both

requestSharedWebCredentials​

function requestSharedWebCredentials(): Promise<never>;
ReturnsPromise<never>ThrowsAlways UNSUPPORTEDPlatformsiOSAndroid

Shared web credentials are an iOS service of rnk with no counterpart in this package. Use the system's Password AutoFill (AuthenticationServices on iOS, Credential Manager on Android), or a password manager.

setSharedWebCredentials​

function setSharedWebCredentials(server: string, username: string, password?: string): Promise<never>;
ReturnsPromise<never>ThrowsAlways UNSUPPORTEDPlatformsiOSAndroid

The same: it rejects at once and stores nothing.

Every function​

rnkThe core call it makes
setGenericPassword(username, password, options)setCredentials({ username, password }, options) in the store of service
getGenericPassword(options)getCredentials({ prompt }) in the store of service
hasGenericPassword(options)hasItem('credentials') in the store of service
resetGenericPassword(options)removeCredentials() in the store of service
getAllGenericPasswordServices()hasItem('credentials') in the default store and in each listed store
setInternetCredentials(server, username, password, options)setCredentials({ username, password }, { server, … })
getInternetCredentials(server, options)getCredentials({ server, prompt })
hasInternetCredentials(server)hasItem('credentials:' + server)
resetInternetCredentials(server)removeCredentials({ server })
getSupportedBiometryType()getBiometryTypes() and getBiometryState()
canImplyAuthentication(options)getBiometryState() and isPasscodeSet()
getSecurityLevel(options)getHardwareSecurityLevel()
isPasscodeAuthAvailable()isPasscodeSet()

The core's calls don't show a prompt unless the table names a read: only getGenericPassword and getInternetCredentials of a protected item do.

See also​