Generic passwords
setGenericPassword and the functions around it, as react-native-keychain 10 has them: a user name and a password per service, kept as the credentials of the store of that service.
import { setGenericPassword, getGenericPassword, hasGenericPassword, resetGenericPassword, getAllGenericPasswordServices } from '@apexnative/keychain/compat';
A generic password is a user name and a password under a service. Here it is the credentials of the store of that service: the password is the secret, and the user name is the item's label, readable without a prompt. Without a service, the store is the default one, keychain.
Usage
import * as Keychain from '@apexnative/keychain/compat';
export async function saveSession(username: string, refreshToken: string) {
await Keychain.setGenericPassword(username, refreshToken, {
service: 'auth',
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
}
export async function restoreSession() {
if (!(await Keychain.hasGenericPassword({ service: 'auth' }))) return null; // no prompt
const credentials = await Keychain.getGenericPassword({
service: 'auth',
authenticationPrompt: { title: 'Sign in to Example', cancel: 'Use password' },
});
return credentials ? credentials.password : null;
}
export async function signOut() {
await Keychain.resetGenericPassword({ service: 'auth' });
}
setGenericPassword
function setGenericPassword(
username: string,
password: string,
options?: SetOptions
): Promise<Result>;
Promise<Result>PromptNever: a write shows no prompt, whatever its accessControlThrowsINVALID_ARGUMENT, UNSUPPORTED, HARDWARE_UNAVAILABLE, and what the device can't do: BIOMETRY_NOT_ENROLLED, PASSCODE_NOT_SET, …PlatformsiOSAndroidWrites the user name and the password of the service, in place of what was there. It calls setCredentials in the store of service, and adds the service to the list of getAllGenericPasswordServices unless it is the default one.
| Parameter | Type | Description |
|---|---|---|
| usernameRequired | string | Kept as the label of the item, outside the secret. Not empty, without U+0000. |
| passwordRequired | string | The secret. Any string without U+0000; for binary data use the core's setBytes. |
| options | SetOptions | Which service, and how to protect the item. See SetOptions. |
| Option | Here |
|---|---|
service | The store of that service; without it, the default store |
accessible | ACCESSIBLE, mapped to the core's Accessible; ALWAYS is UNSUPPORTED |
accessControl | ACCESS_CONTROL, mapped to the core's AccessControl; APPLICATION_PASSWORD is UNSUPPORTED |
cloudSync | iOS: synchronizable, the item goes to iCloud Keychain |
accessGroup | iOS: the access group of the store |
securityLevel | Android: SECURE_HARDWARE fails with HARDWARE_UNAVAILABLE on a device that keeps keys in software; ANY and SECURE_SOFTWARE change nothing |
storage | UNSUPPORTED, any value |
authenticationPrompt | Ignored: a write shows no prompt |
It resolves with the service and the storage, as rnk does: { service, storage }, where storage is 'keychain' on iOS and 'KeystoreAESGCM' on Android.
await Keychain.setGenericPassword('ivan', 'secret');
// { service: 'com.example.app', storage: 'keychain' }
The core's checks apply: an empty user name, U+0000 in a string, an empty service, or cloudSync with an accessControl is INVALID_ARGUMENT. See Options and checks.
getGenericPassword
function getGenericPassword(options?: GetOptions): Promise<UserCredentials | false>;
Promise<UserCredentials | false>PromptWhen the item was written with an accessControlThrowsWhat the prompt ends with: USER_CANCELED, LOCKOUT, KEY_INVALIDATED, …; UNSUPPORTED for APPLICATION_PASSWORDPlatformsiOSAndroidReads the user name and the password of the service, or false when there are none. It calls getCredentials in the store of service.
| Parameter | Type | Description |
|---|---|---|
| options | GetOptions | Which service, and the texts of the prompt. See GetOptions. |
const credentials = await Keychain.getGenericPassword({ authenticationPrompt: 'Unlock your account' });
// { service: 'com.example.app', username: 'ivan', password: 'secret', storage: 'keychain' } or false
authenticationPromptas a string is the title. As an object it takestitle,subtitle,description(Android) andcancel. Without it, the prompt has rnk's texts:Authenticate to retrieve secretandCancel.- The
accessControlof the options is checked and then ignored: the item keeps the protection it was written with.authenticationTypeis ignored too. - A prompt that doesn't end in success rejects with a
KeychainErrorand its code:USER_CANCELED,LOCKOUT,KEY_INVALIDATED. See Errors.
hasGenericPassword
function hasGenericPassword(options?: BaseOptions): Promise<boolean>;
Promise<boolean>PromptNeverPlatformsiOSAndroidWhether the service has a generic password. It calls hasItem('credentials') in the store of service, so a protected item is found without a prompt.
if (await Keychain.hasGenericPassword({ service: 'auth' })) showFaceIdButton();
resetGenericPassword
function resetGenericPassword(options?: BaseOptions): Promise<boolean>;
Promise<boolean>PromptNeverPlatformsiOSAndroidDeletes the generic password of the service and takes the service off the list of getAllGenericPasswordServices. It calls removeCredentials in the store of service. It resolves true, also when there was nothing to delete; the other items of the store stay.
await Keychain.resetGenericPassword({ service: 'auth' }); // true
getAllGenericPasswordServices
function getAllGenericPasswordServices(options?: GetAllOptions): Promise<string[]>;
Promise<string[]>PromptNeverPlatformsiOSAndroidThe services that have a generic password now: the app's default service first, when the default store has credentials, then the others, sorted.
await Keychain.setGenericPassword('a', '1');
await Keychain.setGenericPassword('b', '2', { service: 'work' });
await Keychain.getAllGenericPasswordServices(); // ['com.example.app', 'work']
The package can't list every store on the device, so the other services come from a list it keeps: the item generic-services in the store apexnative.keychain.compat. setGenericPassword and the migration add a service to it; resetGenericPassword removes it. A service on the list is answered only while its credentials are still there, so credentials removed through the core API drop out too.
What this list can't know:
- A store whose credentials were written by the core's
setCredentials, not through the layer or the migration, is not listed, except the default one. - rnk's own items aren't listed before they are migrated: the layer reads only this package's items.
skipUIAuth is accepted and changes nothing: listing never shows a prompt.