Clayton Francis Entrepreneur, software developer, and avid learner. Let’s share the knowledge!

React Native and Expo SecureStore: Encrypt local data

11 min read 3081

react native local encryption

What is AsyncStorage?

You could think of AsyncStorage as local storage for React Native. That’s because it is! As described on React Native’s website: “AsyncStorage is an unencrypted, asynchronous, persistent, key-value storage system that is global to the app.”

It’s a mouthful. But simply put, it allows you to save data locally on the user’s device. Say you want to recall a users theme setting or allow them to pick up where they left off after restarting their phone or the app, being able to persist data offline makes AsyncStorage your best friend!

Why you shouldn’t use AsyncStorage for sensitive data

If, on the other hand, you need to store sensitive data — i.e., a JWT token — AsyncStorage is that best friend who always gets you in trouble. Remember, the data that you save with AsyncStorage is unencrypted, so anyone with access can read it, which isn’t good for sensitive data.

Encrypted data storage alternatives

For sensitive information, we need an encrypted and secure way of storing data locally. Luckily, we have options:

All three of these are great options to use, but in this article, we’re going to cover Expo SecureStore.

Expo is a wonderful SDK with several fabulous libraries, although you need to configure unimodules to use Expo with a Bare React App. But once it’s done, the world is your oyster.

Creating a React Native project

If you would like to have a look the final code, you can find it on my GitHub:

To initialize your project, paste the following into Terminal:

npx react-native init yourAppNameHere --template react-native-template-typescript

This creates a new React Native project with a TypeScript template. But don’t take my word for it, let’s build the apps and see for yourself.

yarn run ios

After some time, you’ll see your shiny new app on your iOS Simulator. Now, for Android:

We made a custom demo for .
No really. Click here to check it out.

yarn run android
/pre>
To use Expo packages in a bare React Native project, we first need to install and configure react-native-unimodules. So in your Terminal, type:
yarn add react-native-unimodules expo-secure-store && cd ios && pod install && cd ..

The above command will install the libraries, navigate to the iOS folder, install your projects’ CocoaPods, then navigate back to your project folder.

💡 Hint: Add a post-install script to your package.json file to save you from manual pod installs:

"scripts": {
    ...
    "postinstall": "cd ios && pod install"
  },

Developing encrypted local storage in iOS

If you’re adding unimodules to an existing project, and, if you’re not familiar with native iOS development, please pay extra attention to where you delete and add new lines of code.

Click here for the full comparison of changes.

AppDelegate.h

First, let’s change the code from this:

#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>

@property (nonatomic, strong) UIWindow *window;

@end

To this:

#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>

#import <UMCore/UMAppDelegateWrapper.h>

@interface AppDelegate : UMAppDelegateWrapper <UIApplicationDelegate, RCTBridgeDelegate>

@property (nonatomic, strong) UIWindow *window;

@end

AppDelegate.m

Now, change your code from:

#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
}
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif

RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"secureStoreExample"
initialProperties:nil];

rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end

To this:

#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import <UMCore/UMModuleRegistry.h>
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
  FlipperClient *client = [FlipperClient sharedClient];
  SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
  [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
  [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
  [client addPlugin:[FlipperKitReactPlugin new]];
  [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
  [client start];
}
#endif

@interface AppDelegate () <RCTBridgeDelegate>

@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#ifdef FB_SONARKIT_ENABLED
  InitializeFlipper(application);
#endif

self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"secureStoreExample"
                                            initialProperties:nil];

  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  [super application:application didFinishLaunchingWithOptions:launchOptions];
  return YES;
}

- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
    NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
    // If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
    return extraModules;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end

Podfile

Next, we’ll take this code:

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '10.0'

target 'secureStoreExample' do
  config = use_native_modules!

  use_react_native!(:path => config["reactNativePath"])

  target 'secureStoreExampleTests' do
    inherit! :complete
    # Pods for testing
  end

  # Enables Flipper.
  #
  # Note that if you have use_frameworks! enabled, Flipper will not work and
  # you should disable these next few lines.
  use_flipper!
  post_install do |installer|
    flipper_post_install(installer)
  end
end

target 'secureStoreExample-tvOS' do
  # Pods for secureStoreExample-tvOS

  target 'secureStoreExample-tvOSTests' do
    inherit! :search_paths
    # Pods for testing
  end
end

And change it to:

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
require_relative '../node_modules/react-native-unimodules/cocoapods.rb'

platform :ios, '10.0'

target 'secureStoreExample' do
  config = use_native_modules! use_unimodules!

  use_react_native!(:path => config["reactNativePath"])

  target 'secureStoreExampleTests' do
    inherit! :complete
    # Pods for testing
  end

  # Enables Flipper.
  #
  # Note that if you have use_frameworks! enabled, Flipper will not work and
  # you should disable these next few lines.
  use_flipper!
  post_install do |installer|
    flipper_post_install(installer)
  end
end

target 'secureStoreExample-tvOS' do
  # Pods for secureStoreExample-tvOS

  target 'secureStoreExample-tvOSTests' do
    inherit! :search_paths
    # Pods for testing
  end
end

Now that we’ve made the necessary changes to the iOS folder, we need to install our CocoaPods again:

cd ios && pod install

At the time of writing, the most recent version of Expo SecureStore wasn’t compatible with CocoaPods. If you run into this issue, install the previous version. In my case:

yarn add [email protected] && cd ios && pod install && cd ..

Android

Click here for the full comparison of changes.

android/build.gradle

Let’s update the code from the following.

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext {
        buildToolsVersion = "29.0.2"
        minSdkVersion = 16
        compileSdkVersion = 29
        targetSdkVersion = 29
    }
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:3.5.3")
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenLocal()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url("$rootDir/../node_modules/react-native/android")
        }
        maven {
            // Android JSC is installed from npm
            url("$rootDir/../node_modules/jsc-android/dist")
        }

        google()
        jcenter()
        maven { url 'https://www.jitpack.io' }
    }
}

And change it to:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext {
        buildToolsVersion = "29.0.2"
        minSdkVersion = 21
        compileSdkVersion = 29
        targetSdkVersion = 29
    }
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:3.5.3")
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenLocal()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url("$rootDir/../node_modules/react-native/android")
        }
        maven {
            // Android JSC is installed from npm
            url("$rootDir/../node_modules/jsc-android/dist")
        }

        google()
        jcenter()
        maven { url 'https://www.jitpack.io' }
    }
}

android/app/build.gradle

From this:

apply plugin: "com.android.application"

import com.android.build.OutputFile

....
//roughly line 183
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    //noinspection GradleDynamicVersion
    implementation "com.facebook.react:react-native:+"  // From node_modules

    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"

    debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
      exclude group:'com.facebook.fbjni'
    }

    debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
        exclude group:'com.squareup.okhttp3', module:'okhttp'
    }

    debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
    }

    if (enableHermes) {
        def hermesPath = "../../node_modules/hermes-engine/android/";
        debugImplementation files(hermesPath + "hermes-debug.aar")
        releaseImplementation files(hermesPath + "hermes-release.aar")
    } else {
        implementation jscFlavor
    }
}

To this:

apply plugin: "com.android.application"
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'

import com.android.build.OutputFile

...
//roughly line 184:
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    //noinspection GradleDynamicVersion
    implementation "com.facebook.react:react-native:+"  // From node_modules

    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
// Add this line here addUnimodulesDependencies()
addUnimodulesDependencies()

    debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
      exclude group:'com.facebook.fbjni'
    }

    debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
        exclude group:'com.squareup.okhttp3', module:'okhttp'
    }

    debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
    }

    if (enableHermes) {
        def hermesPath = "../../node_modules/hermes-engine/android/";
        debugImplementation files(hermesPath + "hermes-debug.aar")
        releaseImplementation files(hermesPath + "hermes-release.aar")
    } else {
        implementation jscFlavor
    }
}

android/settings.gradle

Now, let’s go from:

rootProject.name = 'secureStoreExample'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'

To this:

rootProject.name = 'secureStoreExample'
apply from: '../node_modules/react-native-unimodules/gradle.groovy'; includeUnimodulesProjects()
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'

android/app/src/main/java/com/yourappname/MainApplication.java

Change your code from:

package com.securestoreexample;

import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };
...

To:

package com.securestoreexample;

import com.secureStoreExample.generated.BasePackageList;

import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;


import java.util.Arrays;

import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
import org.unimodules.core.interfaces.SingletonModule;
public class MainApplication extends Application implements ReactApplication {
  private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(new BasePackageList().getPackageList(), null);

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          // Add unimodules
          List<ReactPackage> unimodules = Arrays.<ReactPackage>asList(
            new ModuleRegistryAdapter(mModuleRegistryProvider)
          );
          packages.addAll(unimodules);
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };
...

Expo SecureStore API

SecureStore has three main API methods: set, get, and delete. Let’s clear out our App.tsx file and explore them. To begin, replace the contents of App.tsx with:

import React, {useState} from 'react';
import {SafeAreaView, StyleSheet, StatusBar, Button, Text} from 'react-native';

const App = () => {

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.container}>
        <Button title="set token" onPress={() => {}} />
        <Button title="get token" onPress={() => {}} />
        <Button title="delete token" onPress={() => {}} />
        <Text>Token will appear here</Text>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default App;

react native secure store tokens

This gives us three buttons in the middle of the screen. They don’t do anything at the moment, so let’s fix that! Like AsyncStorage, SecureStore uses key-value pairs, and as we’re using TypeScript we can utilize enums:

import React, {useState} from 'react';
import {SafeAreaView, StyleSheet, StatusBar, Button, Text} from 'react-native';

export enum SecureStoreEnum {
  TOKEN = 'token',
}

const App = () => {
  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.container}>
        <Button title="set token" onPress={() => {}} />
        <Button title="get token" onPress={() => {}} />
        <Button title="delete token" onPress={() => {}} />
        <Text>{token}</Text>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default App;

We use this enum (key) to pair the value that we’re going to store on our device.

Next, we’ll configure a useState hook to store our state variable — value retrieved from our device — plus the mock variable that we will be storing.

import React, {useState} from 'react';
import {SafeAreaView, StyleSheet, StatusBar, Button, Text} from 'react-native';

export enum SecureStoreEnum {
  TOKEN = 'token',
}

const App = () => {
  const [token, setToken] = useState<string>('');
  const fakeToken = 'A fake token 🍪';



  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.container}>
        <Button title="set token" onPress={() => {}} />
        <Button title="get token" onPress={() => {}} />
        <Button title="delete token" onPress={() => {}} />
        <Text>{token}</Text>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default App;

You can view the code in this gist.

SecureStore.setItemAsync (key, value);

Okay, everything’s set up, now we’re really cooking with gas! Time to play with SecureStore. 😃

Because SecureStore is asynchronous, we need to call it in an async function. Add the following to App.tsx:

import React, {useState} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  StatusBar,
  Button,
  Text,
  Alert,
} from 'react-native';
import * as SecureStore from 'expo-secure-store';

export enum SecureStoreEnum {
  TOKEN = 'token',
}

const App = () => {
  const [token, setToken] = useState<string>('');
  const fakeToken = 'A fake token 🍪';

  const handleSetToken = async () => {
    SecureStore.setItemAsync(SecureStoreEnum.TOKEN, fakeToken).then;
    setToken(fakeToken);
  };

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.container}>
        <Button title="set token" onPress={handleSetToken} />
        <Button title="get token" onPress={() => {}} />
        <Button title="delete token" onPress={() => {}} />
        <Text>{token}</Text>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default App;

setItemAsync uses the enum that we created earlier as the key to the value that we’re saving, A fake token 🍪. It then waits for the function to complete. Once our asynchronous function is complete, we save the same variable to our useState hook with setToken.

Now, if you click set token, you’ll see our fake token appear below the three buttons.

react native local storage tokens

SecureStore.getItemAsync(key);

Next, we will give our “get token” button some functionality so we can retrieve the value that we’ve stored on our device. Copy and paste this into App.tsx:

import React, {useState} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  StatusBar,
  Button,
  Text,
  Alert,
} from 'react-native';
import * as SecureStore from 'expo-secure-store';

export enum SecureStoreEnum {
  TOKEN = 'token',
}

const App = () => {
  const [token, setToken] = useState<string>('');
  const fakeToken = 'A fake token 🍪';

  const handleSetToken = async () => {
    await SecureStore.setItemAsync(SecureStoreEnum.TOKEN, fakeToken);
    setToken(fakeToken);
  };

  const handleGetToken = async () => {
    const tokenFromPersistentState = await SecureStore.getItemAsync(
      SecureStoreEnum.TOKEN,
    );
    if (tokenFromPersistentState) {
      Alert.alert(
        "This token is stored on your device, isn't that cool!:",
        tokenFromPersistentState,
      );
    }
  };

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.container}>
        <Button title="set token" onPress={handleSetToken} />
        <Button title="get token" onPress={handleGetToken} />
        <Button title="delete token" onPress={() => {}} />
        <Text>{token}</Text>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default App;

We use an if statement on line 29 to ensure that the alert box will only open if a value is retrieved.

Now for the magic: close your app or restart your device and click on get token. Ta-da! Your token is back.

react native storage secure token

SecureStore.deleteItemAsync(key);

If this were a real-world application, we’d need to be able to delete the users token when they log out. Last piece of code, I promise 😇.

import React, {useState} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  StatusBar,
  Button,
  Text,
  Alert,
} from 'react-native';
import * as SecureStore from 'expo-secure-store';

export enum SecureStoreEnum {
  TOKEN = 'token',
}

const App = () => {
  const [token, setToken] = useState<string>('');
  const fakeToken = 'A fake token 🍪';

  const handleSetToken = async () => {
    await SecureStore.setItemAsync(SecureStoreEnum.TOKEN, fakeToken);
    setToken(fakeToken);
  };

  const handleGetToken = async () => {
    const tokenFromPersistentState = await SecureStore.getItemAsync(
      SecureStoreEnum.TOKEN,
    );
    if (tokenFromPersistentState) {
      Alert.alert(
        "This token is stored on your device, isn't that cool!:",
        tokenFromPersistentState,
      );
    }
  };

  const handleDeleteToken = async () => {
    await SecureStore.deleteItemAsync(SecureStoreEnum.TOKEN);
    setToken('');
  };

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.container}>
        <Button title="set token" onPress={handleSetToken} />
        <Button title="get token" onPress={handleGetToken} />
        <Button title="delete token" onPress={handleDeleteToken} />
        <Text>{token}</Text>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default App;

Here, handleDeleteToken on line 37 deletes the token then sets the token state to an empty string (initial state).

Now your data’s secure and encrypted!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Clayton Francis Entrepreneur, software developer, and avid learner. Let’s share the knowledge!

Leave a Reply