PromleeBlog
sitemap
aboutMe

posting thumbnail
React Native(Expo)환경에서 SecureStore 사용하기
How to use SecureStore in React Native(Expo)

📅

🚀

들어가기 전에 🔗

이 글은 기본적인 Expo 프로젝트가 생성되어 있다고 가정합니다. Expo 프로젝트를 생성하는 방법은 Expo 프로젝트 생성:윈도우, Mac을 참고해주세요.
또한 기본적인 React Native CLI 개발 환경에서도 추가 설정만으로 사용 가능합니다.

SecureStore란? 🔗

expo secure store은 키-값 쌍을 암호화하여 기기에 로컬로 안전하게 저장하는 방법을 제공합니다. 각 엑스포 프로젝트에는 별도의 저장 시스템이 있으며 다른 엑스포 프로젝트의 저장소에는 액세스할 수 없습니다. (공식문서)
React Native에는 기기 자체에 key-value로 데이터를 저장할 수 있는 AsyncStorage가 있지만, 왜 Expo에서는 SecureStore를 추가하여 사용하는지 궁금할 수 있습니다. 이름에도 나와있듯이 SecureStore는 AsyncStorage보다 더 안전하게 데이터를 저장할 수 있다고 합니다.
Android에서 값은 Android의 키 저장소 시스템으로 암호화된 SharedPreferences에 저장됩니다. iOS에서 값은 키체인 서비스를 사용하여 kSecClassGenericPassword로 저장됩니다. iOS에는 값을 가져올 수 있는 시기를 제어하는 값의 kSecAttrAccessible 속성을 설정할 수 있는 추가 옵션이 있습니다.

SecureStore 만의 특징 🔗

expo-secure-store를 사용하여 저장한 데이터는 앱 제거 시 같이 삭제됩니다. 또한 새 지문을 추가하는 등 사용자의 생체 인식 설정이 변경되면 requireAuthentication 옵션을 true로 설정하여 보호된 모든 데이터에 액세스할 수 없게 됩니다.

🚀

SecureStore 설치 및 설정 🔗

다음 명령어를 통해 SecureStore를 설치합니다.
npx expo install expo-secure-store

Expo 환경 설정 🔗

Expo 프로젝트의 app.json 파일에 다음과 같이 설정을 추가합니다.
app.json
{
  "expo": {
    "plugins": [
      [
        "expo-secure-store",
        {
          "configureAndroidBackup": true,
          "faceIDPermission": "Allow $(PRODUCT_NAME) to access your Face ID biometric data."
        }
      ]
    ]
  }
}
 

React Native CLI 환경 설정 🔗

React Native CLI 환경에서는 다음 명령어만 실행하면 됩니다.
npx pod-install

애플 심사 대비 암호화 설정 추가 🔗

Apple 앱 스토어 연결에서 앱이 구현하는 암호화 알고리즘 유형을 선택하라는 메시지가 표시됩니다. 이를 수출 규정 준수 정보라고 합니다. 앱을 게시하거나 테스트 비행에 제출할 때 이 메시지가 표시됩니다.
다음 사진처럼 앱이 애플 심사에 들어가게 되면 다음과 같은 항목이 있습니다. 앱에 어떤 암호화가 사용되었는지 명시해야 하는 항목입니다.
image
expo-secure-store를 사용하는 경우 앱 구성에서 ios.config.usesNonExemptEncryption 속성을 false로 설정하여 암호화를 사용하지 않는다고 코드 상으로 명시할 수 있습니다.
app.json
{
  "expo": {
    "ios": {
      "config": {
        "usesNonExemptEncryption": false
      }
    }
  }
}

🚀

SecureStore 사용하기 🔗

SecureStore를 사용하기 위해 페이지 컴포넌트에 다음과 같이 코드르 추가합니다.
/app/index.tsx 코드 보기
/app/index.tsx
import * as SecureStore from 'expo-secure-store';
import { useState } from 'react';
import { Text, View, StyleSheet, TextInput, Button, Alert } from 'react-native';
 
async function save(key: string, value: string) {
  await SecureStore.setItemAsync(key, value);
}
 
async function getValueFor(key: string) {
  let result = await SecureStore.getItemAsync(key);
  if (result) {
    Alert.alert("🔐 Here's your value 🔐 \n" + result);
  } else {
    Alert.alert('No values stored under that key.');
  }
}
 
const App = () => {
  const [key, onChangeKey] = useState('Your key here');
  const [value, onChangeValue] = useState('Your value here');
 
  return (
    <View style={styles.container}>
      <Text style={styles.paragraph}>Save an item, and grab it later!</Text>
      {}
 
      <TextInput
        style={styles.textInput}
        clearTextOnFocus
        onChangeText={(text) => onChangeKey(text)}
        value={key}
      />
      <TextInput
        style={styles.textInput}
        clearTextOnFocus
        onChangeText={(text) => onChangeValue(text)}
        value={value}
      />
      {}
      <Button
        title="Save this key/value pair"
        onPress={() => {
          save(key, value);
          onChangeKey('Your key here');
          onChangeValue('Your value here');
        }}
      />
      <Text style={styles.paragraph}>🔐 Enter your key 🔐</Text>
      <TextInput
        style={styles.textInput}
        onSubmitEditing={(event) => {
          getValueFor(event.nativeEvent.text);
        }}
        placeholder="Enter the key for the value you want to get"
      />
    </View>
  );
};
 
export default App;
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: 10,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  paragraph: {
    marginTop: 34,
    margin: 24,
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  textInput: {
    height: 35,
    borderColor: 'gray',
    borderWidth: 0.5,
    padding: 4,
  },
});
image
위 코드에서는 SecureStore.setItemAsync(key, value)로 값을 저장하고, SecureStore.getItemAsync(key)로 값을 가져오는 방법을 보여줍니다. 일반적인 AsyncStorage와 사용법이 비슷하지만, 더 안전하게 데이터를 저장할 수 있다는 장점이 있습니다.
SecureStore.deleteItemAsync(key, options) 메서드를 사용하여 특정 키의 값을 삭제할 수 있습니다. 또한 SecureStore.deleteAllItemsAsync() 메서드를 사용하여 모든 값을 삭제할 수 있습니다.

🚀

생체 인식 추가하기 🔗

SecureStore는 생체 인식을 추가할 수 있는 기능을 제공합니다. 다음과 같이 requireAuthentication 속성으로 생체 인식 여부, authenticationPrompt 속성으로 인증 메시지를 추가할 수 있습니다.
SecureStore.canUseBiometricAuthentication() 메서드를 사용하여 기기에서 생체 인식을 사용할 수 있는지 확인할 수 있습니다.
/app/index.tsx
// ...
async function save(key: string, value: string) {
	if (SecureStore.canUseBiometricAuthenticationAsync()) {
		await SecureStore.setItemAsync(key, value, {
			requireAuthentication: true,
			authenticationPrompt: 'Authenticate to save the value',
		});
	} else {
		await SecureStore.setItemAsync(key, value);
	}
}
// ...
여기서 플랫폼별로 차이가 나타납니다. Android에서는 모든 작업에 사용자 인증이 필요합니다. iOS에서는 기존 값을 읽거나 업데이트할 때만(새 값을 만들 때는 안 됨) 사용자에게 인증 메시지를 표시합니다.
또한 Simulator에서는 생체 인식이 지원되지 않으므로 실제 기기에서 테스트해야 합니다.

🚀

결론 🔗

SecureStore과 AsyncStorage을 사용해야 하는 경우를 잘 구분해서 사용하면 불필요한 리소스 낭비를 막을 수 있어보입니다. 여러 로그인 토큰, 사용자 정보 등을 저장할 때는 SecureStore를 사용하고, 간단한 설정값, 테마 등을 저장할 때는 AsyncStorage를 사용하는 것이 좋을 것 같습니다.

더 생각해 보기 🔗

참고 🔗