Secure Store
Encrypted storage for small secrets such as sign-in tokens and API keys. On iOS values go into the Keychain. On Android they are encrypted with an AES-256-GCM key that lives in the Android Keystore. You can require Face ID, Touch ID or a fingerprint before a value is read.
- pyxpo-secure-store
- v0.1.0a1
- Native: Swift + Kotlin
- Like expo-secure-store
Install
Install the package into the same environment as pyxpo.
Add secure-store to modules in your app config, then import it in your code.
[tool.pyxpo]
modules = ["secure-store"]import pyxpo_secure_store as SecureStoreThis module has native code. After you add it, run pyxpo run or pyxpo build again so it is compiled into the app. Hot reload alone is not enough.
Platforms
Quick start
import pyxpo_secure_store as SecureStore
async def sign_in(token):
await SecureStore.set_item_async("auth.token", token)
async def current_token():
return await SecureStore.get_item_async("auth.token") # str or None
async def sign_out():
await SecureStore.delete_item_async("auth.token")Configuration
These options set the text iOS shows when it asks the user for access. Each has a default, so you only set the ones you want to change.
| Option | Info.plist key | Default |
|---|---|---|
faceIDPermission | NSFaceIDUsageDescription | Use Face ID to unlock your saved sign-in details. |
[tool.pyxpo.module_options.secure-store]
faceIDPermission = "Use Face ID to unlock your saved sign-in details."Permissions
pyxpo adds these to Info.plist and AndroidManifest.xml for you when the module is listed.
| iOS | NSFaceIDUsageDescription, used only when you set requireAuthentication. |
|---|---|
| Android | USE_BIOMETRIC. |
API reference
Functions
All functions are coroutines. camelCase aliases exist: getItemAsync, setItemAsync, deleteItemAsync, isAvailableAsync.
await set_item_async(key, value, options=None)Saves a string. Use json.dumps for objects.
await get_item_async(key, options=None)→ str | NoneReads a value. Returns None when the key is missing.
await delete_item_async(key, options=None)Removes a value.
await is_available_async()→ boolTrue on iOS and Android when the module is linked. False on the desktop.
await can_use_biometric_authentication_async()→ boolTrue when the device can protect values with biometrics.
Types
Results and options are plain Python dicts with these keys.
Options
| Key | Type | Description |
|---|---|---|
keychainService | str | Groups values. Use the same value to read what you wrote. |
requireAuthentication | bool | Asks for biometrics before reading. Android 10 or newer. |
authenticationPrompt | str | Text shown in the biometric prompt. |
keychainAccessible | int | iOS only. When the value can be read. Use one of the constants below. |
accessGroup | str | iOS only. Keychain access group shared with other apps. |
keychainAccessible constants
| Key | Type | Description |
|---|---|---|
AFTER_FIRST_UNLOCK | 0 | After the first unlock since restart. |
AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY | 1 | Same, and never moved to a new device. |
ALWAYS | 2 | Always readable. |
WHEN_PASSCODE_SET_THIS_DEVICE_ONLY | 3 | Only while a passcode is set. |
ALWAYS_THIS_DEVICE_ONLY | 4 | Always, never moved to a new device. |
WHEN_UNLOCKED | 5 | Only while the device is unlocked. |
WHEN_UNLOCKED_THIS_DEVICE_ONLY | 6 | Same, and never moved to a new device. |
Examples
Protect a value with biometrics
Ask for Face ID or a fingerprint before the value is returned.
import pyxpo_secure_store as SecureStore
PROTECTED = {
"requireAuthentication": True,
"authenticationPrompt": "Unlock your account",
}
async def save_pin(pin):
if await SecureStore.can_use_biometric_authentication_async():
await SecureStore.set_item_async("pin", pin, PROTECTED)
async def read_pin():
return await SecureStore.get_item_async("pin", PROTECTED)Store a small object
Values are strings, so encode objects as JSON.
import json
import pyxpo_secure_store as SecureStore
async def save_session(session):
await SecureStore.set_item_async("session", json.dumps(session))
async def load_session():
raw = await SecureStore.get_item_async("session")
return json.loads(raw) if raw else NoneGood to know
- Keys may contain letters, digits, '.', '-' and '_'.
- Keep values to a few KB. Use pyxpo-storage or pyxpo-file-system for larger data.
- On iOS, Keychain items stay after the app is deleted. Clear them on first launch if that matters to you.
- On the desktop preview values are kept in memory only, so you can test the flow without a device.