SecureStorage omitting keychainService option

What are the consequences of excluding the keychainService option when using Expo.SecureStore?

You may omit it. Specifying the service just lets you namespace your entries.

1 Like

Hi, I have found this answer but I am still not sure I understand what setting the keychainService does.
Can you please give a bit more explanation ?

As per this SO answer (link), for a keychain item of class kSecClassGenericPassword, the primary key is the combination of kSecAttrAccount and kSecAttrService (Expo’s keychainService).

So, if you set the service when storing a key-value pair, then you will have to declare that same kSecAttrService in order to retrieve the key.

HI, could you please provide a basic example of the usage of this options map? For instance for saving some regular username/password credentials. I have been reading a lot of documentation but I’m still confused about how to use it. And incredibly I couldn’t find any example of its usage on the Web. Many thanks in advanced.

After some code tests, I found out how SecureStore works. Hopefully the code below will be of help.
Please check out JavaScript Promises if you’re not familiar with .then(), .catch(), ect.

( Maybe, some expo developer could add this example in the docs ? )

import React from "react";
import { View, TouchableOpacity, Text, TextInput } from "react-native";
import * as SecureStore from "expo-secure-store";

/*
    Consider the SecureStore and keychainService pack as being similar to a
    JavaScript Object (or JSON) where:
    - a keychainService is like a nested object with it's own entries
    - entries specified without a keychainService are stored on the default keychainService
      (in Expo the default keychainService is named 'app')
    - keychainAccessible (iOS only) specifies in which conditions the specified
      item and keychainService are NOT undefined
    - you can NOT read entries that are not in the default keychainService without specifying the
      name of the keychainService
    - you can only store item whose value is a string (or can be serialized to a string)
    - you can only read items through getItemAsync()
    - you can only store/change items through setItemAsync()
    - you can only delete items through deleteItemAsync()
    - deleted or items that do not exist always have a value of  null  (JS null, not a string!)
*/

/*
    The code that follows after this comment could be part of a SecureStore like this:

    const SecureStore = {
        "credentials": {
            "email": "some@email.value"
        },
        "settings":{
            "email": "this is not the same as in 'credentials' keychainService"
            "show_notifications": "true",
            "color_scheme": "dark",
            "email_signature": "Sent from my Expo app"
        },
        "app": {
            "this_entry": "is not part of any keychain, so it's store in the default one",
            "asWellAs": "this other entry",
            "email": "this is not the same as in 'credentials' or 'settings' keychainService"
        }
    }
*/
const secureStoreOptions = {
    keychainService: "credentials",
    keychainAccessible: SecureStore.ALWAYS // iOS only
};

class TestSecureStore extends React.Component {
    constructor(props) {
        super(props);

        this.state = { email: "" };
    }

    storeEmailInState = (email) => { this.setState({ email }); }

    loadEmailFromSecureStore = () => {
        SecureStore.getItemAsync("email", secureStoreOptions)
            .then((itemValue) => {
                if (item === null) {
                    // All items are stored only as strings
                    // A returned value of  null  (NOTE: not string!) means the item does not exist
                    console.log("The item does not exist in SecureStore");
                } else {
                    console.log("SecureStore: Successfully loaded item! It's value is:", itemValue);
                    this.setState({ email: itemValue });
                }
            }).catch((error) => {
                console.log("SecureStore: An error occurred while loading the item...", error);
            });
    }
    
    storeEmailInSecureStore = ()=> {
        SecureStore.setItemAsync("email", this.state.email, secureStoreOptions)
            .then(() => {
                console.log("SecureStore: Successfully stored item!");
            }).catch((error) => {
                console.log("SecureStore: An error occurred while storing the item...", error);
            });
    }
    
    deleteEmailInSecureStore = () => {
        // To delete an entry in SecureStore, you cannot set that item to null
        // using something like: SecureStore.setItemAsync("some_item", null, ... )
        // deleteItemAsync is the way to go
        SecureStore.deleteItemAsync("email", secureStoreOptions)
            .then(() => {
                console.log("SecureStore: Successfully deleted item!");
                this.setState({ email: "" });
            }).catch((error) => {
                console.log("SecureStore: An error occurred while deleting the item...", error);
            });
    }
    
    render = () => {
        return <View style={{ flex: 1 }}>
            <TextInput
                style={{ width: "100%", backgroundColor: "#EEE", borderWidth: 1 }}
                placeholder="Type your email"
                onChangeText={this.storeEmailInState}
                value={this.state.email}
            />
            <TouchableOpacity onPress={ this.storeEmailInSecureStore }
                style={{ backgroundColor: "#DDD", padding: 15 }}>
                <Text>SAVE</Text>
            </TouchableOpacity>
            <TouchableOpacity onPress={ this.loadEmailFromSecureStore }
                style={{ backgroundColor: "#DDD", padding: 15 }}>
                <Text>LOAD</Text>
            </TouchableOpacity>
            <TouchableOpacity onPress={ this.deleteEmailInSecureStore }
                style={{ backgroundColor: "#DDD", padding: 15 }}>
                <Text>DELETE</Text>
            </TouchableOpacity>
        </View>;
    }
}

@cristian-nxtl Your answer is pretty great, but I would like to correct You on the first point of Your explanation. When You store a value via the Expo SecureStorage, but You don’t specify the keychain service, the default keychainService value is used. As You can see in the library’s native source code on GitHub (link), the default value is app.

As I’ve mentioned in my pervious answer, each keychain item of class kSecClassGenericPassword has two attributes as the primary key: keychain service (kSecAttrService) and keychain account/key (kSecAttrAccount).

Thanks for the heads up! I’ll adjust my previous answer to reflect that!

1 Like