Popular Searches
Popular Course Categories
Popular Courses

Secure Storage Using Keychain or SecureStore

Secure Storage Using Keychain or SecureStore

Local Storage and Offline Data Management

Secure Storage Using Keychain or SecureStore in React Native

Course: React Native Training

Topic: Secure Storage Using Keychain or SecureStore

Course Link: React Native Training at JustAcademy

Demo Link: Register for a Free Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to Secure Storage

Secure storage is an important part of mobile application development. React Native applications often need to save sensitive information such as authentication tokens, refresh tokens, API credentials, session identifiers, and other confidential values. Storing these values in ordinary local storage can expose them to risks if the application or device is compromised.

Secure storage libraries allow developers to save small pieces of sensitive information using platform-provided security mechanisms. On iOS, these mechanisms commonly use Keychain Services. On Android, secure storage solutions can use encrypted preferences and the Android Keystore system.

Two commonly used approaches in React Native are:

  • Keychain-based storage: Uses iOS Keychain Services and Android-compatible secure credential storage through a library such as React Native Keychain.
  • Expo SecureStore: Provides a unified key-value API for securely storing small values on iOS and Android.

The React Native framework does not provide a built-in secure-storage API, so developers normally use a suitable native library. Read the React Native security documentation.

2. Learning Objectives

After completing this topic, you should be able to:

  • Understand the purpose of secure storage in mobile applications.
  • Explain the difference between AsyncStorage and secure storage.
  • Understand iOS Keychain Services and Android Keystore.
  • Install and configure Expo SecureStore in a React Native application.
  • Save, retrieve, and delete sensitive values.
  • Store authentication tokens safely after login.
  • Understand biometric authentication options and their limitations.
  • Handle errors and missing secure-storage values.
  • Apply security best practices to authentication and logout flows.

3. What Is Secure Storage?

Secure storage is a mechanism for storing confidential application data using security facilities provided by the mobile operating system. Unlike ordinary key-value storage, secure-storage libraries are designed to protect sensitive values through platform-specific mechanisms.

3.1 Examples of Sensitive Data

Data Type Example Recommended Approach
Access token A short-lived token used to authorize API requests Secure storage
Refresh token A credential used to obtain a new access token Secure storage with appropriate server-side controls
Session identifier A value representing an authenticated session Secure storage when it functions as a credential
User interface preferences Theme or language selection AsyncStorage is generally sufficient
Cached product list Non-confidential catalogue information AsyncStorage or a database
Payment card security code CVV or CVC Do not persist it in local storage
Passwords User login password Avoid storing the raw password; use a secure authentication flow

3.2 Why Is Secure Storage Necessary?

  • Confidentiality: Helps protect sensitive values against unauthorized access.
  • Platform security: Uses operating-system facilities instead of relying only on application-level storage.
  • Session persistence: Can keep a user authenticated across application restarts when the authentication design allows it.
  • Reduced exposure: Helps prevent credentials from being casually exposed through ordinary application data files.
  • Safer authentication: Supports secure handling of tokens used to communicate with backend services.

4. Understanding iOS Keychain Services

Keychain Services is an Apple platform facility for storing small pieces of sensitive information. It is commonly used for passwords, cryptographic keys, certificates, and authentication-related data.

4.1 How Keychain Works

  1. The React Native application requests that a value be stored.
  2. A native library communicates with the iOS Keychain APIs.
  3. The operating system stores the item according to the configured access and protection attributes.
  4. The application requests the value when it is needed and the applicable access conditions are satisfied.

4.2 Common Keychain Use Cases

  • Storing refresh tokens.
  • Keeping small authentication credentials.
  • Protecting sensitive session information.
  • Storing cryptographic material when the chosen API and security design support it.

4.3 Keychain Persistence Considerations

Keychain items may persist beyond an application's uninstall and reinstall cycle in some circumstances. Therefore, developers must not assume that uninstalling the application always removes every credential. The exact behavior depends on the platform, configuration, and library. Design logout, account removal, and credential revocation flows explicitly.

5. Understanding Android Keystore

Android Keystore is a system for managing cryptographic keys. It can help keep key material outside the application's ordinary storage and, on supported devices, can use hardware-backed protection.

5.1 How Android Secure Storage Works

  1. The application requests secure storage through a library.
  2. The library uses the relevant Android platform mechanisms.
  3. Secret values may be encrypted and stored in app-private storage, while cryptographic keys are managed through Android Keystore.
  4. The application retrieves values through the library when access is permitted.

Android Keystore is not simply a general-purpose text database. It is primarily a key-management system. Libraries such as Expo SecureStore combine Android platform facilities to provide a convenient secure key-value interface.

5.2 Important Android Considerations

  • Security characteristics vary by device, Android version, and library configuration.
  • Do not assume that every device uses hardware-backed key protection.
  • App uninstallation generally removes Expo SecureStore values on Android.
  • Backup and device-transfer behavior must be configured and tested appropriately.

6. Choosing Between React Native Keychain and Expo SecureStore

Both libraries can provide secure storage, but the right choice depends on your project architecture, authentication requirements, and native configuration.

Feature Expo SecureStore React Native Keychain
Main purpose Secure key-value storage Credential and Keychain-oriented secure storage
Typical API setItemAsync(), getItemAsync(), deleteItemAsync() Methods such as setGenericPassword(), getGenericPassword(), and resetGenericPassword()
Platform support iOS and Android, with supported Expo environments iOS and Android, subject to package and native setup requirements
Integration Convenient for Expo applications Useful for projects requiring Keychain-specific credential options
Data model String key-value pairs Credential-oriented API with username/password-shaped values and additional options
Biometric and access options Supports authentication-related options where available Offers platform-specific access-control and authentication options depending on version and configuration
Best fit Small secrets with a simple API Applications needing more credential-storage configuration

For the examples below, Expo SecureStore is used because it provides a straightforward API. If your application uses React Native Keychain, follow that library's official documentation instead of mixing the two APIs.

7. Installing Expo SecureStore

7.1 Installation Command

For an Expo project, install the compatible version using the Expo CLI:

npx expo install expo-secure-store

This command selects a version compatible with the installed Expo SDK. In a bare React Native project, ensure Expo modules are configured before using Expo SecureStore. For native iOS projects, follow the installation instructions for your setup and run the required CocoaPods installation when applicable.

7.2 Import the Library

import * as SecureStore from 'expo-secure-store';

7.3 Official Installation Resources

8. Saving Sensitive Data with SecureStore

The setItemAsync() method stores a value under a specified key. Both the key and value should be strings.

8.1 Basic Example

import * as SecureStore from 'expo-secure-store';

async function saveAccessToken(token) {
  try {
    await SecureStore.setItemAsync('access_token', token);
    console.log('Access token saved securely.');
  } catch (error) {
    console.error('Unable to save access token:', error);
  }
}

8.2 Explanation

  • SecureStore.setItemAsync() performs the storage operation asynchronously.
  • access_token is the key used to identify the stored value.
  • token is the sensitive value returned by a trusted authentication flow.
  • try...catch handles errors that may occur during storage.

Never log real access tokens or refresh tokens. The success message above deliberately does not print the secret itself.

9. Retrieving Data from SecureStore

Use getItemAsync() to retrieve a previously stored value. If the key does not exist, the method normally resolves to null.

import * as SecureStore from 'expo-secure-store';

async function getAccessToken() {
  try {
    const token = await SecureStore.getItemAsync('access_token');

    if (token === null) {
      console.log('No access token found.');
      return null;
    }

    return token;
  } catch (error) {
    console.error('Unable to retrieve access token:', error);
    return null;
  }
}

9.1 When to Retrieve a Token

  • When the application starts and needs to check an existing session.
  • Before making an authenticated API request, when the architecture requires reading the token at that point.
  • During a session-restoration process.
  • When preparing an authentication state after a successful app restart.

Do not assume that finding a token means the session is still valid. The server may have revoked or expired it. Validate the session using the backend's authentication rules.

10. Deleting Sensitive Data

When a user logs out or a credential is no longer required, remove the associated value from secure storage.

import * as SecureStore from 'expo-secure-store';

async function deleteAccessToken() {
  try {
    await SecureStore.deleteItemAsync('access_token');
    console.log('Access token deleted.');
  } catch (error) {
    console.error('Unable to delete access token:', error);
  }
}

10.1 Why Deletion Matters

  • It reduces the chance of accidentally reusing a stale credential.
  • It supports logout and account-switching workflows.
  • It helps remove secrets that are no longer required.
  • It prevents unnecessary credential persistence on a shared device.

Deleting a local token does not automatically invalidate it on the backend. Where supported, revoke or invalidate the session on the server as part of logout.

11. Using SecureStore with Login Authentication

A typical login workflow sends the user's credentials to a backend authentication endpoint. If authentication succeeds, the server returns a token or another supported session credential. The application stores the appropriate token in secure storage rather than ordinary unencrypted storage.

11.1 Authentication Workflow

  1. The user enters their credentials.
  2. The application sends the credentials to the backend over HTTPS.
  3. The backend validates the credentials.
  4. The backend returns the session information or token.
  5. The application stores the appropriate secret using SecureStore.
  6. The application updates its in-memory authentication state.
  7. Authenticated requests use the token according to the API's requirements.
  8. During logout, the application clears local credentials and performs server-side revocation when available.

11.2 Example: Saving a Token After Login

import * as SecureStore from 'expo-secure-store';

async function loginUser(email, password) {
  try {
    const response = await fetch('https://api.example.com/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ email, password })
    });

    if (!response.ok) {
      throw new Error('Login failed. Please check your credentials.');
    }

    const result = await response.json();

    if (!result.accessToken) {
      throw new Error('The server did not return an access token.');
    }

    await SecureStore.setItemAsync(
      'access_token',
      result.accessToken
    );

    return {
      success: true,
      user: result.user ?? null
    };
  } catch (error) {
    console.error('Login error:', error.message);
    return {
      success: false,
      message: error.message
    };
  }
}

Important: Replace the example API URL and response structure with your real backend contract. Never ship a production application with a placeholder endpoint or embed server secrets in the mobile app. The example assumes the backend uses an accessToken field.

12. Using SecureStore in a React Native Component

Secure storage operations are asynchronous. In a component, manage loading and error states so the interface remains understandable while the application retrieves a value.

import React, { useEffect, useState } from 'react';
import { View, Text, Button, ActivityIndicator } from 'react-native';
import * as SecureStore from 'expo-secure-store';

export default function SessionScreen() {
  const [loading, setLoading] = useState(true);
  const [hasToken, setHasToken] = useState(false);
  const [errorMessage, setErrorMessage] = useState('');

  useEffect(() => {
    let active = true;

    async function loadSession() {
      try {
        const token = await SecureStore.getItemAsync('access_token');

        if (active) {
          setHasToken(token !== null);
        }
      } catch (error) {
        if (active) {
          setErrorMessage('Unable to check the saved session.');
        }
      } finally {
        if (active) {
          setLoading(false);
        }
      }
    }

    loadSession();

    return () => {
      active = false;
    };
  }, []);

  async function handleLogout() {
    try {
      await SecureStore.deleteItemAsync('access_token');
      setHasToken(false);
      setErrorMessage('');
    } catch (error) {
      setErrorMessage('Unable to clear the saved session.');
    }
  }

  if (loading) {
    return ;
  }

  return (
    
      
        {hasToken ? 'A saved token exists.' : 'No saved token found.'}
      

      {errorMessage ? {errorMessage} : null}

      {hasToken ? (
        

This component only checks whether a token exists. It does not prove that the token is valid or that the user is currently authorized. A production application should validate the session with its backend and keep authentication state consistent across the app.

13. Biometric Authentication and Secure Storage

Some secure-storage APIs support access controls that require the user to authenticate with a supported device mechanism, such as Face ID, Touch ID, or Android biometric authentication. These features are platform-dependent and require careful testing.

13.1 Example with Expo SecureStore

import * as SecureStore from 'expo-secure-store';

async function saveProtectedValue(value) {
  try {
    await SecureStore.setItemAsync(
      'protected_value',
      value,
      {
        requireAuthentication: true
      }
    );

    return true;
  } catch (error) {
    console.error('Unable to save protected value:', error);
    return false;
  }
}

async function readProtectedValue() {
  try {
    return await SecureStore.getItemAsync(
      'protected_value',
      {
        requireAuthentication: true
      }
    );
  } catch (error) {
    console.error('Unable to read protected value:', error);
    return null;
  }
}

13.2 Important Biometric Considerations

  • Support and behavior depend on the operating system, device, library version, and configuration.
  • On iOS, Face ID may require the appropriate usage-description configuration.
  • Expo Go has limitations for some biometric-protected SecureStore operations. Test in an appropriate development or production build.
  • Changes to enrolled biometrics may make some protected values inaccessible.
  • Do not assume that biometric protection replaces backend authentication or session validation.
  • Provide a sensible recovery and reauthentication flow when secure values become inaccessible.

14. Configuring Expo SecureStore for iOS

Expo projects that use Continuous Native Generation can configure SecureStore using its config plugin. For example, the plugin can configure the Face ID permission description when biometric authentication is used.

{
  "expo": {
    "plugins": [
      [
        "expo-secure-store",
        {
          "faceIDPermission": "Allow this app to access Face ID."
        }
      ]
    ]
  }
}

This is an illustrative configuration fragment. Merge it into the existing Expo configuration rather than replacing other project settings. Native configuration changes may require rebuilding the application. If your project does not use the config plugin workflow, follow the documentation for manual native configuration.

15. Storing Multiple Sensitive Values

You can use different keys for different credentials. Clear key names make storage operations easier to maintain.

import * as SecureStore from 'expo-secure-store';

const TOKEN_KEY = 'access_token';
const REFRESH_TOKEN_KEY = 'refresh_token';

export async function saveSessionTokens(accessToken, refreshToken) {
  try {
    await SecureStore.setItemAsync(TOKEN_KEY, accessToken);
    await SecureStore.setItemAsync(REFRESH_TOKEN_KEY, refreshToken);
  } catch (error) {
    console.error('Unable to save session tokens.');
    throw error;
  }
}

export async function readSessionTokens() {
  try {
    const accessToken = await SecureStore.getItemAsync(TOKEN_KEY);
    const refreshToken = await SecureStore.getItemAsync(REFRESH_TOKEN_KEY);

    return { accessToken, refreshToken };
  } catch (error) {
    console.error('Unable to read session tokens.');
    throw error;
  }
}

export async function clearSessionTokens() {
  try {
    await SecureStore.deleteItemAsync(TOKEN_KEY);
    await SecureStore.deleteItemAsync(REFRESH_TOKEN_KEY);
  } catch (error) {
    console.error('Unable to clear session tokens.');
    throw error;
  }
}

SecureStore does not provide a general database transaction for multiple independent keys. If saving one token succeeds and saving the next fails, the storage may be partially updated. For important authentication workflows, consider the order of operations, handle partial failures, and avoid treating a partially saved session as valid.

16. SecureStore vs AsyncStorage

React Native applications commonly use both ordinary local storage and secure storage. Choose the library based on the sensitivity and size of the information.

Feature AsyncStorage SecureStore or Keychain-Based Storage
Encryption Not encrypted by default Uses platform secure-storage mechanisms
Typical data Preferences, simple caches, non-sensitive application state Tokens, credentials, small secrets
Storage model General persistent key-value storage Secure storage for small values
Large datasets Not ideal for large databases, but often more suitable than secure storage for ordinary cached data Not designed for large payloads or databases
Biometric access control Not provided as a built-in security feature May be supported by the chosen library and platform
Best use Non-confidential data Confidential authentication data and small secrets

Do not put an entire application state tree into AsyncStorage if it contains credentials or other sensitive values. Keep secrets out of ordinary persistence, debugging output, and analytics events.

17. Error Handling and Troubleshooting

Problem Possible Cause Recommended Solution
Package cannot be found Package is not installed or native project setup is incomplete Install the compatible package and follow the native setup instructions.
Value is missing The key was never saved or the value was deleted Handle null and provide a sign-in or recovery flow.
Save operation fails Native storage error, unsupported configuration, or payload too large Catch the error, check the payload size, and avoid storing large objects.
Biometric prompt does not appear Device capability, configuration, or development-environment limitation Check device support, permissions, build configuration, and official documentation.
Stored value becomes inaccessible Access-control conditions or changes in biometric enrollment Handle the error and require reauthentication when appropriate.
User remains logged in after local logout Backend session was not revoked or another credential remains Clear all relevant local credentials and revoke the session server-side when supported.
Works on one platform but not another Different platform behavior or native configuration Test on both iOS and Android devices and review platform-specific requirements.

18. Security Best Practices

  1. Store only what is necessary: Keep the amount of sensitive information in local storage to a minimum.
  2. Never store raw passwords: Use a trusted authentication system and persist only the credentials required by the session design.
  3. Use HTTPS: Protect authentication requests and API traffic in transit.
  4. Never log secrets: Do not print access tokens, refresh tokens, passwords, or sensitive responses in production logs.
  5. Handle token expiration: Refresh or reauthenticate according to the backend's rules.
  6. Clear credentials on logout: Remove relevant local secrets and revoke server-side sessions where supported.
  7. Protect app state: Avoid copying secrets into Redux persistence, AsyncStorage, crash reports, or analytics.
  8. Use short-lived access tokens: Where supported by the backend, limit the period in which a stolen access token remains useful.
  9. Protect refresh tokens: Apply rotation, revocation, and reuse-detection policies where supported by the authentication system.
  10. Test on real devices: Verify the behavior of native storage and biometric features on supported devices.
  11. Plan for recovery: Secure values can become unavailable; support a controlled reauthentication flow.
  12. Do not rely on local storage alone: The backend must still validate authorization and session status.

19. Common Mistakes to Avoid

  • Saving tokens in plain text using AsyncStorage.
  • Assuming secure storage makes a compromised device completely safe.
  • Logging a token to debug why an API request failed.
  • Assuming a saved token is valid without checking its expiry or server-side status.
  • Forgetting to clear local credentials during account switching.
  • Assuming that deleting an app always deletes all Keychain data.
  • Storing large JSON documents in a secure-storage API intended for small values.
  • Using biometric authentication without a fallback or reauthentication plan.
  • Embedding private backend keys in JavaScript code shipped with the application.
  • Ignoring failures when saving or deleting credentials.

20. Practical Project: Secure Login Session Manager

Build a small React Native feature that securely stores a session token and allows the user to check and clear the saved session.

Project Requirements

  • Install and import Expo SecureStore.
  • Save a token after a simulated or real login flow.
  • Read the saved token when the screen loads.
  • Display whether a token exists without showing the secret itself.
  • Handle loading and error states.
  • Delete the saved token when the user logs out.
  • For a real backend, validate the session and revoke it during logout when supported.

Example: A Simple Session Utility

import * as SecureStore from 'expo-secure-store';

const SESSION_KEY = 'session_token';

export async function saveSessionToken(token) {
  if (typeof token !== 'string' || token.length === 0) {
    throw new Error('A valid token is required.');
  }

  await SecureStore.setItemAsync(SESSION_KEY, token);
}

export async function getSessionToken() {
  return SecureStore.getItemAsync(SESSION_KEY);
}

export async function removeSessionToken() {
  await SecureStore.deleteItemAsync(SESSION_KEY);
}

This utility centralizes access to the token and makes it easier to replace the implementation or add consistent error handling later. In a production application, call these functions from an authentication service and coordinate them with the app's in-memory session state.

Expected Result

  • After successful login, the token is saved using secure storage.
  • After restarting the application, the app can attempt to restore the session.
  • If no token is found, the app can direct the user to the login screen.
  • After logout, the local token is removed.
  • If storage fails, the app displays a safe error message without exposing sensitive details.

21. Practice Exercises

  1. Install Expo SecureStore in a React Native Expo project.
  2. Create a function to save an access token.
  3. Create a function to retrieve the token and handle a missing value.
  4. Add a logout button that removes the token.
  5. Show a loading indicator while the app checks for a saved session.
  6. Simulate a storage error and display a useful message.
  7. Compare SecureStore with AsyncStorage using examples of sensitive and non-sensitive data.
  8. Research the biometric options available for the library and platform you use.
  9. Extend the project to validate the session with a backend.
  10. Document how your application handles token expiry, logout, and reauthentication.

22. Interview Questions and Answers

Q1. What is secure storage in React Native?

Answer: Secure storage is a way to save small sensitive values using platform security mechanisms, such as iOS Keychain Services and Android secure-storage facilities.

Q2. Does React Native include secure storage by default?

Answer: No. Developers generally install a library such as Expo SecureStore or React Native Keychain to access native secure-storage capabilities.

Q3. What is iOS Keychain?

Answer: Keychain Services is an Apple platform facility for storing sensitive information such as credentials, certificates, and cryptographic keys.

Q4. What is Android Keystore?

Answer: Android Keystore manages cryptographic keys and can provide stronger protection for key material. Secure-storage libraries can combine it with other Android storage mechanisms.

Q5. What is Expo SecureStore?

Answer: Expo SecureStore is a library that provides an asynchronous key-value API for storing small values securely on supported mobile platforms.

Q6. How do you save data using Expo SecureStore?

Answer: Use SecureStore.setItemAsync(key, value), passing a string key and a string value.

Q7. How do you retrieve a saved value?

Answer: Use SecureStore.getItemAsync(key). Handle null when no value exists and catch errors that may occur.

Q8. How do you delete a saved value?

Answer: Use SecureStore.deleteItemAsync(key) to remove the item associated with that key.

Q9. What is the difference between AsyncStorage and SecureStore?

Answer: AsyncStorage is unencrypted by default and is suited to non-sensitive persistent data. SecureStore is designed for small sensitive values and uses platform secure-storage mechanisms.

Q10. Can SecureStore save objects directly?

Answer: Its key-value API accepts strings. Serializable objects can be encoded with JSON.stringify() and decoded with JSON.parse(), but only small necessary values should be stored.

Q11. Can secure storage replace backend authentication?

Answer: No. Secure storage helps protect local credentials, but the backend must still validate tokens, expiry, permissions, and session status.

Q12. Does biometric authentication guarantee complete application security?

Answer: No. Biometrics can add an access-control layer, but they do not replace secure API communication, server-side authorization, token management, and safe application design.

Q13. What should happen during logout?

Answer: Clear the relevant local credentials, reset in-memory authentication state, and revoke the backend session when the authentication system supports it.

Q14. What happens to Expo SecureStore data after app uninstallation?

Answer: Behavior differs by platform. Android values are not preserved after uninstallation, while iOS Keychain items may persist across reinstallation with the same bundle identifier. Applications should not rely on uninstall behavior as their credential-deletion strategy.

Q15. Why should errors be handled when using SecureStore?

Answer: Native storage operations can fail because of platform conditions, configuration, access-control requirements, or storage constraints. Error handling prevents silent failures and allows safe recovery.

23. Quick Revision Checklist

  • Secure storage protects small sensitive values using native platform mechanisms.
  • iOS provides Keychain Services for credentials and other secrets.
  • Android Keystore manages cryptographic keys and may support hardware-backed protection.
  • Expo SecureStore uses setItemAsync(), getItemAsync(), and deleteItemAsync().
  • SecureStore key-value values should be strings.
  • AsyncStorage is not encrypted by default and should not be used for secrets.
  • Tokens must never be printed in logs or placed in analytics events.
  • A saved token must not be assumed to be valid without checking session rules.
  • Logout should clear local credentials and revoke server-side sessions where supported.
  • Biometric behavior and data persistence vary by platform and configuration.
  • Always test secure-storage behavior on supported iOS and Android devices.

24. Conclusion

Secure storage is a fundamental part of developing safer React Native applications. Libraries such as Expo SecureStore and React Native Keychain provide APIs for using platform-specific security mechanisms to store small sensitive values. They are particularly useful for authentication tokens and session credentials that should not be placed in ordinary unencrypted storage.

Choose a library that matches your project, handle asynchronous operations and errors, and implement a complete authentication lifecycle that includes token expiry, logout, server-side validation, and recovery. Secure storage is an important layer of protection, but it must be combined with secure API communication, proper backend authorization, and careful handling of sensitive data throughout the application.

25. Additional Learning Resources

whatsapp