我正在使用 Supabase 实现 Google 登录我的 Expo 应用程序,我将其设置为将令牌存储在 expo-secure-store 上,但是当我登录时,我收到以下警告:
向 SecureStore 提供的值大于 2048 字节。尝试存储此类值将在 SDK 35 中引发错误。
我正在使用 Expo 48。我尝试存储的令牌如下(已编辑):
{"access_token":"<REDACTED>","token_type":"<REDACTED>","expires_in":<REDACTED>,"refresh_token":"<REDACTED>","user":{"id":"<REDACTED>","aud":"<REDACTED>","role":"<REDACTED>","email":"<REDACTED>","email_confirmed_at":"<REDACTED>","phone":"<REDACTED>","confirmed_at":"<REDACTED>","last_sign_in_at":"<REDACTED>","app_metadata":{"provider":"<REDACTED>","providers":["<REDACTED>"]},"user_metadata":{"avatar_url":"<REDACTED>","email":"<REDACTED>","email_verified":<REDACTED>,"full_name":"<REDACTED>","iss":"<REDACTED>","name":"<REDACTED>","picture":"<REDACTED>","provider_id":"<REDACTED>","sub":"<REDACTED>"},"identities":[{"id":"<REDACTED>","user_id":"<REDACTED>","identity_data":{"avatar_url":"<REDACTED>","email":"<REDACTED>","email_verified":<REDACTED>,"full_name":"<REDACTED>","iss":"<REDACTED>","name":"<REDACTED>","picture":"<REDACTED>","provider_id":"<REDACTED>","sub":"<REDACTED>"},"provider":"<REDACTED>","last_sign_in_at":"<REDACTED>","created_at":"<REDACTED>","updated_at":"<REDACTED>"}],"created_at":"<REDACTED>","updated_at":"<REDACTED>"},"expires_at":<REDACTED>}
我想我不是第一个尝试通过 Expo 实现 Google 登录的人,所以我想知道这个问题的普遍接受的解决方案是什么?
我们已经阐明了 SecureStore 的用法,并概述了用于加密会话数据的选项:https://supabase.com/docs/guides/getting-started/tutorials/with-expo?auth-store=secure-store#initialize -a-react-native-app
这使用常规
async-storage
进行实际值存储,但对存储在那里的值进行加密。
然后我们将这些值与随机加密的密钥相关联,然后将其保存到 SecureStore。加密密钥对于 SecureStore 来说始终足够小,并且通过安全地存储密钥,我们还可以保护异步存储中的加密数据
import { createClient } from '@supabase/supabase-js'
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
import * as aesjs from 'aes-js';
import 'react-native-get-random-values';
// As Expo's SecureStore does not support values larger than 2048
// bytes, an AES-256 key is generated and stored in SecureStore, while
// it is used to encrypt/decrypt values stored in AsyncStorage.
class LargeSecureStore {
private async _encrypt(key: string, value: string) {
const encryptionKey = crypto.getRandomValues(new Uint8Array(256 / 8));
const cipher = new aesjs.ModeOfOperation.ctr(encryptionKey, new aesjs.Counter(1));
const encryptedBytes = cipher.encrypt(aesjs.utils.utf8.toBytes(value));
await SecureStore.setItemAsync(key, aesjs.utils.hex.fromBytes(encryptionKey));
return aesjs.utils.hex.fromBytes(encryptedBytes);
}
private async _decrypt(key: string, value: string) {
const encryptionKeyHex = await SecureStore.getItemAsync(key);
if (!encryptionKeyHex) {
return encryptionKeyHex;
}
const cipher = new aesjs.ModeOfOperation.ctr(aesjs.utils.hex.toBytes(encryptionKeyHex), new aesjs.Counter(1));
const decryptedBytes = cipher.decrypt(aesjs.utils.hex.toBytes(value));
return aesjs.utils.utf8.fromBytes(decryptedBytes);
}
async getItem(key: string) {
const encrypted = await AsyncStorage.getItem(key);
if (!encrypted) { return encrypted; }
return await this._decrypt(key, encrypted);
}
async removeItem(key: string) {
await AsyncStorage.removeItem(key);
await SecureStore.deleteItemAsync(key);
}
async setItem(key: string, value: string) {
const encrypted = await this._encrypt(key, value);
await AsyncStorage.setItem(key, encrypted);
}
}
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY
const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: new LargeSecureStore(),
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
});
也许不是理想的答案,但我最终从supabase返回的会话对象中删除了不太敏感的用户元数据:
function removeUserMetaData(itemValue: string) {
let parsedItemValue = JSON.parse(itemValue);
// Remove properties from the object
if (parsedItemValue) {
delete parsedItemValue.user?.identities;
delete parsedItemValue.user?.user_metadata;
}
// Convert the modified object back to a JSON string
return JSON.stringify(parsedItemValue);
}
const ExpoSecureStoreAdapter = {
getItem: async (key: string) => {
return SecureStore.getItemAsync(key);
},
setItem: (key: string, value: string) => {
SecureStore.setItemAsync(key, removeUserMetaData(value));
},
removeItem: (key: string) => {
SecureStore.deleteItemAsync(key);
},
};
并将用户元数据存储在单独的非安全存储中。