Storing Credentials Using React Native Keychain

Secure your mobile app's most sensitive data with platform-native encryption. Learn how react-native-keychain integrates iOS Keychain Services and Android Keystore for robust credential protection.

Why Secure Storage Matters

Mobile apps handle sensitive data daily--from authentication tokens to user credentials. But where should you store them? The answer isn't Async Storage.

React Native doesn't provide secure storage natively. The framework emphasizes using platform-specific solutions: iOS Keychain Services and Android Keystore/Encrypted Shared Preferences. The react-native-keychain library provides a unified API that abstracts these differences while leveraging native encryption mechanisms.

The Consequences of Improper Storage

Storing credentials in unencrypted locations creates serious security vulnerabilities. Mobile applications are surprisingly easy to reverse-engineer--anything embedded in your JavaScript code or stored in Async Storage can be extracted from the app bundle. Attackers use rooted or jailbroken devices, debugging tools, and binary inspection techniques to access unprotected data.

When credentials are exposed, the consequences extend beyond individual account compromise. Data breaches can lead to widespread unauthorized access, identity theft, and privacy violations. User trust, once eroded, is difficult to rebuild. Beyond the immediate security impact, organizations face regulatory scrutiny under frameworks like GDPR and PIPEDA for failing to adequately protect user data.

The solution lies in leveraging hardware-backed security where available. iOS Keychain Services and Android Keystore use secure hardware processors to protect cryptographic keys, making credential extraction significantly more difficult even on compromised devices. react-native-keychain provides the bridge between your JavaScript code and these platform-native security mechanisms.

For comprehensive mobile security implementation, consider how these credential storage patterns integrate with security headers and HTTPS configurations to create a complete defense-in-depth strategy for your applications.

Storage option comparison for React Native apps
Storage TypeUse CaseEncryptionAccess Control
Async StorageUser preferences, cached UI stateNoneApp sandbox only
react-native-keychainTokens, passwords, API keysNative platform encryptionBiometric, device lock
Encrypted Shared PreferencesAndroid-specific secure storageAES-256Keystore-backed

Installation and Setup

react-native-keychain supports iOS and Android platforms with a simple installation process. The library handles native module linking automatically in React Native 0.60+.

Installation

# npm
npm install react-native-keychain

# yarn
yarn add react-native-keychain

# pnpm
pnpm add react-native-keychain

iOS Configuration

No additional native configuration is required for basic usage. The library uses iOS Keychain Services API automatically. For iOS 9.0+ deployment targets, ensure your project is configured appropriately. If you need to share credentials between your app and extensions, you'll configure Keychain Access Groups in Xcode.

Android Configuration

For Android, ensure your minSdkVersion is 23 or higher to access Android Keystore functionality. No additional dependencies are required.

Gradle Configuration

// android/build.gradle
android {
 defaultConfig {
 minSdkVersion 23
 targetSdkVersion 34
 }
}

ProGuard/R8 Rules

Add these rules to your proguard-rules.pro file to ensure proper obfuscation and prevent issues in release builds:

# react-native-keychain
-keep class org.gradle.** { *; }
-keepclassmembers class * extends com.facebook.react.bridge.JavaScriptModule {
 <methods>;
}
-keepclassmembers class * extends com.facebook.react.bridge.NativeModule {
 <init>(...);
}
-keepclassmembers class com.facebook.react.uimanager.ViewManager {
 <fields>;
}

These rules prevent reflection issues and ensure the library functions correctly when code is minified for production releases.

Basic Generic Password Storage
1import * as Keychain from 'react-native-keychain';2 3// Store generic credentials4const storeCredentials = async (username: string, password: string) => {5 try {6 await Keychain.setGenericPassword(username, password);7 console.log('Credentials stored successfully');8 } catch (error) {9 console.error('Failed to store credentials:', error);10 }11};12 13// Retrieve credentials14const getCredentials = async () => {15 try {16 const credentials = await Keychain.getGenericPassword();17 if (credentials) {18 console.log('Username:', credentials.username);19 console.log('Password:', credentials.password);20 return credentials;21 } else {22 console.log('No credentials found');23 return null;24 }25 } catch (error) {26 console.error('Failed to retrieve credentials:', error);27 return null;28 }29};

Core API Methods

Generic Password Storage

The setGenericPassword() and getGenericPassword() methods store and retrieve arbitrary credential pairs. These methods accept an optional service parameter that creates logical groupings for your credentials, preventing conflicts between different features or integrations in your application.

Using the service parameter is a best practice for organizing credentials. For example, you might use 'auth' for authentication tokens, 'api' for third-party API keys, and 'user' for general user credentials. This separation makes it easier to manage access controls and clear specific credential sets without affecting others.

When handling errors in production applications, consider the common scenarios: keychain unavailable on older devices or emulators, storage quota exceeded, and security constraint violations. Always use try-catch blocks with specific error types and provide user-friendly fallback messages when security features aren't available.

Internet Credentials

The setInternetCredentials() and getInternetCredentials() methods are designed specifically for server-based authentication scenarios. They store credentials associated with particular server endpoints, making them ideal for HTTP Basic Auth patterns, OAuth endpoint credentials, and server-specific authentication tokens.

Use internet credentials when your application needs to authenticate against known servers repeatedly. The library associates stored credentials with the server URL, automatically retrieving the correct credentials when needed. This approach is particularly useful for enterprise applications that connect to internal APIs or services requiring stored authentication state.

However, be cautious about what credentials you store for server access. Avoid storing highly privileged credentials and implement server-side measures to limit the impact of potential credential exposure. For most modern applications, token-based authentication with properly scoped permissions provides better security than storing raw credentials.

For React Native component patterns that integrate secure authentication flows, explore our guide on building responsive React Native interfaces while maintaining security best practices across your component architecture.

Keychain Security Features

Platform-native protection for your most sensitive data

Biometric Authentication

Integrate Face ID, Touch ID, and fingerprint authentication to require user presence for credential access.

Hardware Encryption

Leverage iOS Keychain Services and Android Keystore for hardware-backed cryptographic key protection.

Access Control

Configure when credentials are accessible--only when unlocked, after first unlock, or device passcode required.

Cross-Platform API

Unified JavaScript API abstracts platform differences while utilizing native encryption mechanisms.

Service Organization

Group credentials by service or feature to prevent conflicts and manage access separately.

Error Handling

Comprehensive error types for graceful degradation when security features are unavailable.

Biometric Authentication

Add an extra layer of security by requiring biometric verification before accessing stored credentials. react-native-keychain supports Face ID, Touch ID, and fingerprint authentication on supported devices.

Enabling Biometric Access

The accessControl option controls when biometric verification is required. The BiometricCurrentSet option requires biometric authentication for every access, providing the highest level of security. The BiometricAny option uses any enrolled biometric without requiring a specific type. For applications that need to support devices without biometric hardware, the DevicePasscode option provides a fallback to device passcode authentication.

import * as Keychain from 'react-native-keychain';

// Store with biometric requirement
await Keychain.setGenericPassword(username, password, {
 service: 'auth',
 accessControl: 'BiometricCurrentSet',
 accessible: 'WhenUnlockedThisDeviceOnly'
});

// Retrieve with biometric prompt
const credentials = await Keychain.getGenericPassword({
 accessControl: 'BiometricCurrentSet'
});

Handling Biometric Unavailability

Gracefully handle scenarios where biometrics aren't available. Not all devices have biometric hardware, users may not have enrolled biometrics, and biometric data can change when new fingerprints or faces are registered. Your application should detect these conditions and provide appropriate fallbacks.

const checkBiometricAvailability = async () => {
 try {
 const type = await Keychain.getSupportedBiometricType();
 return { available: true, type };
 } catch (error) {
 // Biometrics not available
 return { available: false, type: null };
 }
};

// Example: Fallback flow
const secureGetCredentials = async () => {
 const { available, type } = await checkBiometricAvailability();
 
 if (!available) {
 // Fallback to device passcode or no authentication
 return await Keychain.getGenericPassword({
 accessControl: 'DevicePasscode'
 });
 }
 
 // Biometric required
 return await Keychain.getGenericPassword({
 accessControl: 'BiometricCurrentSet'
 });
};

This approach ensures your application remains functional across all devices while providing enhanced security where biometric hardware is available.

Biometric Authentication Integration
1import * as Keychain from 'react-native-keychain';2 3// Store credentials with biometric requirement4const storeWithBiometrics = async (username: string, password: string) => {5 try {6 await Keychain.setGenericPassword(username, password, {7 service: 'auth',8 accessControl: 'BiometricCurrentSet',9 accessible: 'WhenUnlockedThisDeviceOnly'10 });11 console.log('Credentials stored with biometric protection');12 } catch (error) {13 console.error('Failed to store credentials:', error);14 }15};16 17// Check available biometric type18const checkBiometrics = async () => {19 try {20 const type = await Keychain.getSupportedBiometricType();21 switch (type) {22 case 'FaceID':23 return 'Face ID';24 case 'TouchID':25 return 'Touch ID';26 case 'Fingerprint':27 return 'Fingerprint';28 default:29 return 'None';30 }31 } catch {32 return 'Not available';33 }34};35 36// Handle biometric unavailability gracefully37const getCredentialsWithFallback = async () => {38 try {39 return await Keychain.getGenericPassword({40 accessControl: 'BiometricCurrentSet'41 });42 } catch (error: any) {43 if (error.code === 'BIOMETRIC_ERROR_NONE_ENROLLED') {44 // Fallback to device passcode45 return await Keychain.getGenericPassword({46 accessControl: 'DevicePasscode'47 });48 }49 throw error;50 }51};

Security Best Practices

Implementing secure credential storage requires attention to access control, error handling, and secure coding practices.

Critical Security Guidelines

  1. Never hardcode credentials - Anything in your app code can be extracted from the app bundle. Even environment files and configuration objects are accessible to determined attackers.
  2. Use appropriate access control - WhenUnlockedThisDeviceOnly provides the strongest protection, with credentials automatically deleted when the app is uninstalled.
  3. Clear credentials on logout - Use resetGenericPassword() to remove sensitive data when users log out or when authentication expires.
  4. Implement proper error handling - Avoid exposing sensitive information in error messages that could aid attackers.
  5. Consider hardware-backed security - On supported devices, keys are stored in secure hardware processors, making extraction significantly more difficult.

Access Control Options

OptionSecurity LeveliOS kSecAttrAccessibleAndroid EquivalentUse Case
WhenUnlockedThisDeviceOnlyHighestkSecAttrAccessibleWhenUnlockedThisDeviceOnlySECURE_STORAGEBanking apps, high-sensitivity data
AfterFirstUnlockThisDeviceOnlyHighkSecAttrAccessibleAfterFirstUnlockThisDeviceOnlyDEVICE_SECUREPersistent authentication tokens
WhenUnlockedMediumkSecAttrAccessibleWhenUnlockedN/AGeneral credentials
AlwaysLowestkSecAttrAccessibleAlwaysN/ANot recommended

On iOS, the accessible property maps directly to kSecAttrAccessible constants that control when Keychain items can be accessed. On Android, the encryption level is determined by whether the device lock is required and whether the Keystore is available.

For maximum security, prefer WhenUnlockedThisDeviceOnly combined with biometric access control. This ensures credentials require both device unlock and biometric verification, with automatic deletion on app uninstall for additional protection.

To further strengthen your application's security posture, consider implementing Next.js security headers alongside mobile security measures for comprehensive protection across all platforms.

Common Implementation Patterns

Token-Based Authentication

Store JWT access tokens and refresh tokens separately with different access control levels. Refresh tokens typically require stronger protection since they can generate new access tokens.

import * as Keychain from 'react-native-keychain';

interface AuthTokens {
 accessToken: string;
 refreshToken: string;
}

const storeTokens = async ({ accessToken, refreshToken }: AuthTokens) => {
 // Store access token - lower protection for frequent access
 await Keychain.setGenericPassword('access_token', accessToken, {
 service: 'auth',
 accessible: 'WhenUnlockedThisDeviceOnly'
 });

 // Store refresh token - biometric protection for maximum security
 await Keychain.setGenericPassword('refresh_token', refreshToken, {
 service: 'auth',
 accessControl: 'BiometricCurrentSet',
 accessible: 'WhenUnlockedThisDeviceOnly'
 });
};

const clearTokens = async () => {
 await Keychain.resetGenericPassword({ service: 'auth' });
};

Session Management

Implement a secure session lifecycle that reads credentials on app startup, caches them in memory for the session duration, and clears everything on logout. Consider re-verifying biometrics for sensitive operations when the app returns from the background.

class SecureSessionManager {
 private static instance: SecureSessionManager;
 private cachedCredentials: { username: string; password: string } | null = null;

 static getInstance(): SecureSessionManager {
 if (!SecureSessionManager.instance) {
 SecureSessionManager.instance = new SecureSessionManager();
 }
 return SecureSessionManager.instance;
 }

 async initializeSession(): Promise<boolean> {
 try {
 const credentials = await Keychain.getGenericPassword({
 accessControl: 'BiometricCurrentSet'
 });
 
 if (credentials) {
 this.cachedCredentials = credentials;
 return true;
 }
 return false;
 } catch {
 return false;
 }
 }

 getCachedCredentials(): { username: string; password: string } | null {
 return this.cachedCredentials;
 }

 async endSession(): Promise<void> {
 this.cachedCredentials = null;
 await Keychain.resetGenericPassword({ service: 'auth' });
 }
}

Performance Considerations

Keychain operations are inherently slower than Async Storage due to cryptographic operations. Optimize performance by reading credentials once at startup or login, caching in memory for the session duration, and re-reading when the app returns from background. Never repeatedly read from the keychain in tight loops. Always show loading states during authentication operations to provide clear user feedback.

For additional React Native performance optimization strategies, explore our guide on Node.js performance clustering for backend optimization techniques that complement your mobile application's security architecture.

Frequently Asked Questions

Can react-native-keychain credentials be accessed on rooted/jailbroken devices?

On rooted or jailbroken devices, determined attackers can potentially access Keychain data. However, hardware-backed security on modern devices and strong access control make extraction significantly more difficult. Always implement additional security measures for highly sensitive data and consider server-side validation.

What happens to credentials when the app is uninstalled?

On iOS with 'WhenUnlockedThisDeviceOnly' access, credentials are automatically deleted when the app is uninstalled. Android behavior varies by device and access control settings. This is a security feature, not a bug--ensure your app can handle re-authentication after reinstall.

How is react-native-keychain different from Expo SecureStore?

react-native-keychain works with bare React Native projects and offers more configuration options including biometric type detection, granular access control, and internet credentials for server-based auth. Expo SecureStore is simpler but only works within the Expo ecosystem and provides less customization.

Should I store API keys in react-native-keychain?

While react-native-keychain provides secure storage, client-side API keys should be avoided when possible. If you must store API keys client-side, use Keychain with strict access control. Ideally, use server-side APIs that don't expose keys to client applications.

Can I share credentials between my app and an app extension?

Yes, by configuring Keychain Access Groups. You'll need to set up your app groups in Xcode and configure the shared access group name when storing credentials. This is useful for sharing authentication state with widget or share extension targets.

Conclusion

Implementing secure credential storage is fundamental to mobile app security. react-native-keychain provides a battle-tested solution that leverages platform-native encryption mechanisms--iOS Keychain Services and Android Keystore--to protect your users' most sensitive data.

Key takeaways:

  • Use the right tool for the job - react-native-keychain for credentials, Async Storage for non-sensitive data
  • Leverage biometric authentication - Add user presence verification for sensitive operations
  • Configure access control appropriately - Balance security with user experience
  • Handle errors gracefully - Provide fallbacks when security features aren't available
  • Clear credentials on logout - Don't leave sensitive data accessible after session end

Mobile security is an evolving landscape. Stay current with platform security updates from Apple and Google, regularly review your app's security implementation, and consider periodic security audits for applications handling sensitive user data. As new biometric features and security APIs become available, update your implementation to take advantage of enhanced protection.

Building secure mobile applications requires careful attention to credential storage, authentication flows, and data protection. By following the practices outlined in this guide, you can create React Native applications that properly protect user credentials while maintaining a smooth user experience. For organizations seeking comprehensive mobile development expertise, our web development team can help implement secure, scalable mobile solutions tailored to your specific requirements.

Need Help Implementing Secure Authentication?

Our team specializes in building secure, scalable mobile applications with modern security practices.