react-native-keychain

repository·master·Indexed 25 days ago

https://github.com/oblador/react-native-keychain

A library providing secure access to the iOS Keychain and Android Keystore for React Native applications. It allows developers to securely store and retrieve sensitive credentials, such as passwords and tokens, using native encryption. Key features include support for biometric authentication (Face ID, Touch ID, Fingerprint), passcode authentication, and shared web credentials on iOS. Supports iOS 9.0+, Android API 23+, macOS Catalyst, and visionOS.

Tokens
7.1K
Snippets
15
Records
28
Agent score
85%

What's inside react-native-keychain

  1. Overview of react-native-keychain

    master

    react-native-keychain

    react-native-keychain provides secure access to the keychain (iOS) and keystore (Android) for React Native applications. It is used to securely store and retrieve sensitive information like passwords, internet credentials, and tokens using native encryption.

    Key Features:

    • Biometric authentication (Face ID, Touch ID, Fingerprint)
    • Passcode authentication
    • Secure storage levels
    • Customizable access and storage options
  2. Understand data persistence and lifecycle behavior

    master

    Data stored via react-native-keychain survives app restarts and updates. However, be aware of the following lifecycle behaviors:

    • App Uninstallation: On most platforms, stored data is erased when the app is uninstalled.
    • iOS Exception: On iOS, stored data may persist even after the app is uninstalled and reinstalled due to how iOS manages keychain storage.
    • Critical Data Warning: Do not use react-native-keychain as the sole source of truth for irreplaceable or critical data, as it is primarily intended for secure credential storage rather than general-purpose database persistence.
  3. Check for existing credentials in the keychain

    master

    Before attempting to retrieve or overwrite credentials, use hasGenericPassword or hasInternetCredentials to verify if data is already stored. This prevents unnecessary error handling or logic for missing data.

    • Use hasGenericPassword with a service identifier to check for generic credentials.
    • Use hasInternetCredentials with a server identifier to check for internet-based credentials.
    import Keychain from 'react-native-keychain';
    
    const isGenericPasswordAvailable = await Keychain.hasGenericPassword({
      service: 'service_key'
    });
    
    const isInternetCredentialAvailable = await Keychain.hasInternetCredentials({
      server: 'https://google.com'
    });
  4. Mock the `react-native-keychain` module in Jest

    master

    Because react-native-keychain relies on native application interfaces, it cannot run in a standard Jest environment without mocking. You must create a mock object that replicates the module's structure, including its enums and functions, to allow your JavaScript/TypeScript tests to execute successfully.

    // keychainMock.ts or keychainMock.js
    
    const keychainMock = {
      SECURITY_LEVEL: {
        SECURE_SOFTWARE: 'MOCK_SECURITY_LEVEL_SECURE_SOFTWARE',
        SECURE_HARDWARE: 'MOCK_SECURITY_LEVEL_SECURE_HARDWARE',
        ANY: 'MOCK_SECURITY_LEVEL_ANY',
      },
      ACCESSIBLE: {
        WHEN_UNLOCKED: 'MOCK_AccessibleWhenUnlocked',
        AFTER_FIRST_UNLOCK: 'MOCK_AccessibleAfterFirstUnlock',
        ALWAYS: 'MOCK_AccessibleAlways',
        WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: 'MOCK_AccessibleWhenPasscodeSetThisDeviceOnly',
        WHEN_UNLOCKED_THIS_DEVICE_ONLY: 'MOCK_AccessibleWhenUnlockedThisDeviceOnly',
        AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: 'MOCK_AccessibleAfterFirstUnlockThisDeviceOnly',
      },
      ACCESS_CONTROL: {
        USER_PRESENCE: 'MOCK_UserPresence',
        BIOMETRY_ANY: 'MOCK_BiometryAny',
        BIOMETRY_CURRENT_SET: 'MOCK_BiometryCurrentSet',
        DEVICE_PASSCODE: 'MOCK_DevicePasscode',
        APPLICATION_PASSWORD: 'MOCK_ApplicationPassword',
        BIOMETRY_ANY_OR_DEVICE_PASSCODE: 'MOCK_BiometryAnyOrDevicePasscode',
        BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE: 'MOCK_BiometryCurrentSetOrDevicePasscode',
      },
      AUTHENTICATION_TYPE: {
        DEVICE_PASSCODE_OR_BIOMETRICS: 'MOCK_AuthenticationWithBiometricsDevicePasscode',
        BIOMETRICS: 'MOCK_AuthenticationWithBiometrics',
      },
      STORAGE_TYPE: {
        FB: 'MOCK_FacebookConceal',
        AES: 'MOCK_KeystoreAESCBC',
        RSA: 'MOCK_KeystoreRSAECB',
        KC: 'MOCK_keychain',
      },
      setGenericPassword: jest.fn().mockResolvedValue({
        service: 'mockService',
        storage: 'mockStorage',
      }),
      getGenericPassword: jest.fn().mockResolvedValue({
        username: 'mockUser',
        password: 'mockPassword',
        service: 'mockService',
        storage: 'mockStorage',
      }),
      resetGenericPassword: jest.fn().mockResolvedValue(true),
      hasGenericPassword: jest.fn().mockResolvedValue(true),
      getAllGenericPasswordServices: jest
        .fn()
        .mockResolvedValue(['mockService1', 'mockService2']),
      setInternetCredentials: jest.fn().mockResolvedValue({
        service: 'mockService',
        storage: 'mockStorage',
      }),
      getInternetCredentials: jest.fn().mockResolvedValue({
        username: 'mockUser',
        password: 'mockPassword',
        service: 'mockService',
        storage: 'mockStorage',
      }),
      resetInternetCredentials: jest.fn().mockResolvedValue(),
      getSupportedBiometryType: jest.fn().mockResolvedValue('MOCK_TouchID'),
      canImplyAuthentication: jest.fn().mockResolvedValue(true),
      getSecurityLevel: jest.fn().mockResolvedValue('MOCK_SECURE_SOFTWARE'),
      isPasscodeAuthAvailable: jest.fn().mockResolvedValue(true)
    };
    
    export default keychainMock;
  5. Install react-native-keychain

    master

    To use react-native-keychain in your React Native project, follow these steps:

    1. Install the package using yarn:
      yarn add react-native-keychain
    2. Install iOS dependencies:
      cd ios && pod install
    3. Re-build your Android and iOS projects.

    Note for FaceID support: If you intend to use FaceID, you must add a NSFaceIDUsageDescription entry to your Info.plist file.

  6. Choose Android security levels for data storage

    master

    On Android, react-native-keychain uses Jetpack DataStore encrypted with the Android Keystore system. You can choose between different security levels based on your sensitivity requirements:

    1. High Security (with Biometric Authentication): Uses AES_GCM (symmetric) or RSA (asymmetric) encryption. This requires biometric protection and is best for passwords, personal data, or sensitive keys.
    2. Medium Security (without Authentication): Uses AES_GCM_NO_AUTH (symmetric) encryption. This does not require biometric requirements and is suitable for cached or non-sensitive encrypted data.
    3. Legacy/Deprecated: AES_CBC is available but is not recommended for new implementations.
  7. Store, retrieve, and reset generic credentials

    master

    Use react-native-keychain to manage simple username and password pairs.

    • Store: Use setGenericPassword(username, password, options) to save credentials.
    • Retrieve: Use getGenericPassword(options) to fetch them. It returns an object containing username and password if found, or null if no credentials exist.
    • Reset: Use resetGenericPassword(options) to delete the stored credentials.

    To ensure you are accessing the correct data, use a consistent service name in the options object for all calls.

    Note: These methods only support strings. To store complex objects, use JSON.stringify() before calling setGenericPassword and JSON.parse() after retrieving the value with getGenericPassword.

    import * as Keychain from 'react-native-keychain';
    
    async () => {
      const username = 'zuck';
      const password = 'poniesRgr8';
    
      // Store the credentials
      await Keychain.setGenericPassword(username, password, {service: 'service_key'});
    
      try {
        // Retrieve the credentials
        const credentials = await Keychain.getGenericPassword({service: 'service_key'});
        if (credentials) {
          console.log(
            'Credentials successfully loaded for user ' + credentials.username
          );
        } else {
          console.log('No credentials stored');
        }
      } catch (error) {
        console.error("Failed to access Keychain", error);
      }
    
      // Reset the stored credentials
      await Keychain.resetGenericPassword({service: 'service_key'});
    };
  8. Mock `react-native-keychain` using a Jest `__mocks__` directory

    master

    To mock the entire module for all tests automatically, use the Jest __mocks__ directory pattern:

    1. Create a __mocks__ directory in your project root.
    2. Create a react-native-keychain folder inside __mocks__.
    3. Create an index.js (or index.ts) file inside that folder containing the keychainMock object and export it using module.exports = keychainMock;.
    // index.ts or index.js inside __mocks__/react-native-keychain
    
    const keychainMock = {
      SECURITY_LEVEL: {
        SECURE_SOFTWARE: 'MOCK_SECURITY_LEVEL_SECURE_SOFTWARE',
        SECURE_HARDWARE: 'MOCK_SECURITY_LEVEL_SECURE_HARDWARE',
        ANY: 'MOCK_SECURITY_LEVEL_ANY',
      },
      // ... rest of the keychainMock object ...
    };
    
    module.exports = keychainMock;
  9. Understand encryption upgrades and downgrades

    master

    Encryption Upgrades

    The library automatically uses the highest possible encryption level when storing a secret. However, it will not automatically upgrade the encryption of an existing secret if a higher standard becomes available. To upgrade, you must re-save the secret.

    Encryption Downgrades

    Automatic downgrading of encryption is not supported by the library as it is considered a "loss of trust" from a security standpoint. If your application requires the ability to downgrade encryption levels, you must implement that logic manually within your application code.

  10. Mock `react-native-keychain` using a Jest Setup File

    master

    To mock the module via a global setup file:

    1. In your jest.config.js, add the path to your setup file in the setupFiles array:
    module.exports = {
      setupFiles: ['<rootDir>/jest.setup.js'],
    };
    1. In your jest.setup.js file, import your mock object and call jest.mock():
    import keychainMock from './path/to/keychainMock';
    
    jest.mock('react-native-keychain', () => keychainMock);
    // jest.config.js
    module.exports = {
      // ... other configurations ...
      setupFiles: ['<rootDir>/jest.setup.js'],
    };
    
    // jest.setup.js
    import keychainMock from './path/to/keychainMock';
    
    jest.mock('react-native-keychain', () => keychainMock);