DEV Community

Cover image for How to fix expo-secure-store crashing on Web (Platform-Agnostic Storage in React Native)
Muhammad Saad Bin Nadeem
Muhammad Saad Bin Nadeem

Posted on

How to fix expo-secure-store crashing on Web (Platform-Agnostic Storage in React Native)

If you are building a universal React Native app using Expo, you’ve probably run into this incredibly frustrating issue when setting up your JWT Authentication.

You install expo-secure-store to safely save your user's auth tokens. You test it on your iOS simulator, and it works flawlessly. You test it on Android, perfect.

But the second you hit w in your terminal to test your app in a web browser, your app completely crashes with an error like:
TypeError: ExpoSecureStore.default.getValueWithKeyAsync is not a function

Why does this happen?
expo-secure-store relies on the native hardware security of mobile devices (Keychain for iOS, Keystore for Android). Web browsers do not have this native hardware. When Expo tries to run native code inside Google Chrome, the browser panics and kills your API requests.

The Solution: A Platform-Agnostic Storage Wrapper
Instead of littering your components with if (Platform.OS === 'web') statements every time you need to check a token, the cleanest enterprise-level solution is to create a centralized storage utility.

This utility will automatically detect the environment. If the user is on a phone, it uses the highly secure SecureStore. If they are on the web, it gracefully falls back to standard localStorage.

Create a file called storage.ts in your services folder:

import * as SecureStore from 'expo-secure-store';
import { Platform } from 'react-native';

// Save Token
export const setToken = async (key: string, value: string) => {
  if (Platform.OS === 'web') {
    localStorage.setItem(key, value);
  } else {
    await SecureStore.setItemAsync(key, value);
  }
};

// Get Token
export const getToken = async (key: string) => {
  if (Platform.OS === 'web') {
    return localStorage.getItem(key);
  } else {
    return await SecureStore.getItemAsync(key);
  }
};

// Delete Token
export const deleteToken = async (key: string) => {
  if (Platform.OS === 'web') {
    localStorage.removeItem(key);
  } else {
    await SecureStore.deleteItemAsync(key);
  }
};
Enter fullscreen mode Exit fullscreen mode

How to use it in your Axios Interceptor
Now, instead of calling SecureStore directly, you just call your new smart utility. Here is how clean it looks inside an Axios interceptor:

import axios from 'axios';
import { getToken } from './storage';

const api = axios.create({
  baseURL: process.env.EXPO_PUBLIC_API_URL,
});

api.interceptors.request.use(async (config) => {
  // Works flawlessly on iOS, Android, AND Web!
  const token = await getToken('userToken');

  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});
Enter fullscreen mode Exit fullscreen mode

Gemini said
Here is the exact technical article to post on Dev.to.

Because Dev.to is a platform strictly for developers, we are not going to make this look like an ad. We are going to teach them how to fix a very annoying React Native bug, and then soft-pitch your Gumroad link at the very end. This builds massive trust.

Title: How to fix expo-secure-store crashing on Web (Platform-Agnostic Storage in React Native)

Body:

If you are building a universal React Native app using Expo, you’ve probably run into this incredibly frustrating issue when setting up your JWT Authentication.

You install expo-secure-store to safely save your user's auth tokens. You test it on your iOS simulator, and it works flawlessly. You test it on Android, perfect.

But the second you hit w in your terminal to test your app in a web browser, your app completely crashes with an error like:
TypeError: ExpoSecureStore.default.getValueWithKeyAsync is not a function

Why does this happen?
expo-secure-store relies on the native hardware security of mobile devices (Keychain for iOS, Keystore for Android). Web browsers do not have this native hardware. When Expo tries to run native code inside Google Chrome, the browser panics and kills your API requests.

The Solution: A Platform-Agnostic Storage Wrapper
Instead of littering your components with if (Platform.OS === 'web') statements every time you need to check a token, the cleanest enterprise-level solution is to create a centralized storage utility.

This utility will automatically detect the environment. If the user is on a phone, it uses the highly secure SecureStore. If they are on the web, it gracefully falls back to standard localStorage.

Create a file called storage.ts in your services folder:

TypeScript
import * as SecureStore from 'expo-secure-store';
import { Platform } from 'react-native';

// Save Token
export const setToken = async (key: string, value: string) => {
if (Platform.OS === 'web') {
localStorage.setItem(key, value);
} else {
await SecureStore.setItemAsync(key, value);
}
};

// Get Token
export const getToken = async (key: string) => {
if (Platform.OS === 'web') {
return localStorage.getItem(key);
} else {
return await SecureStore.getItemAsync(key);
}
};

// Delete Token
export const deleteToken = async (key: string) => {
if (Platform.OS === 'web') {
localStorage.removeItem(key);
} else {
await SecureStore.deleteItemAsync(key);
}
};
How to use it in your Axios Interceptor
Now, instead of calling SecureStore directly, you just call your new smart utility. Here is how clean it looks inside an Axios interceptor:

TypeScript
import axios from 'axios';
import { getToken } from './storage';

const api = axios.create({
baseURL: process.env.EXPO_PUBLIC_API_URL,
});

api.interceptors.request.use(async (config) => {
// Works flawlessly on iOS, Android, AND Web!
const token = await getToken('userToken');

if (token) {
config.headers.Authorization = Bearer ${token};
}
return config;
});
Skip the Boilerplate
I got so tired of wiring up this storage utility, Axios interceptors, JWT handling, and .NET AuthControllers for every single new project that I finally packaged it all together.

If you want to skip the 40 hours of tedious "infrastructure" setup and just start building your actual mobile app today, I released my full Enterprise Mobile Solution (React Native/Expo + .NET 9 API + SQLite).

It has all of this built-in, plus premium UI components, for just $39.

You can grab the full source code here: https://cutt.ly/rtAQiXmT

Happy coding! Let me know if you handle your universal storage differently in the comments.

reactnative #dotnet #webdev #tutorial

Top comments (0)