ISecureStorage
A key-value store that encrypts sensitive string values, such as authentication tokens, and keeps them in the device's secure storage. It provides methods that save, get, and delete a value by key, and a method that deletes all values this app has saved. Saved values persist even after the app restarts.
- Interface:
ISecureStorage - Namespace:
Hive.Axyl.Storage - Package:
com.com2usplatform.hiveaxyl.storage
Registration and retrieval
using Hive.Axyl.Core;
using Hive.Axyl.Core.Unity; // HiveBootstrap
using Hive.Axyl.Storage;
var config = CoreConfig.CreateBuilder("{appId}").Build();
HiveBootstrap.Initialize(config, builder =>
{
builder.AddSecureStorage();
});
if (HiveCore.TryResolve<ISecureStorage>(out var storage))
{
// Code that runs only in players built for supported platforms
}
Not registered in the Unity Editor
ISecureStorage is registered only in players built for Android, Windows, iOS, and macOS. It is not registered in the Unity Editor or on other platforms, so using HiveCore.Resolve<T>() throws RegistrationNotFoundException. Always check with TryResolve<T>() before you use it.
Storage method by platform
Values are encrypted and saved in the platform's secure storage, separated by the App ID used for initialization. If you change the App ID, you cannot read values saved with the previous App ID.
| Platform | Storage location | Encryption method |
|---|---|---|
| Android | Jetpack DataStore file in the app-only data area | Encrypted with AES-256-GCM using a Tink key protected by an Android Keystore key. |
| iOS, macOS | Keychain | Keychain encrypts the values. They become accessible after the first unlock following a device restart, and they are not moved to other devices. |
| Windows | Per-App ID folder under %APPDATA%\HiveAxyl | Encrypted with the Windows Data Protection API (DPAPI) based on the current Windows user account. File names use the SHA-256 hash of the key instead of the key. |
Check the following for each platform.
iOS, macOS
- Values that remain after the app is deleted: Values saved in Keychain may remain even after the app is deleted, so do not trust credentials read after the app is reinstalled without checking that they are valid.
- Calls before the first unlock after a device restart: Returns
AccessDenied
The Keychain Sharing entitlement is added to the app target automatically at build time. Calling from an app signed without this entitlement returns a Failure whose Code is FailedPrecondition. The entitlement is not added to macOS builds that do not create an Xcode project, so check that you included the keychain-access-groups entitlement when you sign the app.
Windows
- Programs run under the same Windows user account: They can decrypt saved values, so if you support environments where several people share one Windows account, delete credentials when the app exits or the user logs out.
- Concurrent access from multiple instances: If you run several instances of the same app and they access the storage at the same time, one of the calls may return
AccessDenied. - Failure to overwrite a value: If a value saved under the same key cannot be replaced with a new value, the existing value remains, and the call returns
AccessDeniedor aFailurewhoseCodeisInternal. - Runtime prerequisites: Microsoft Visual C++ 2015-2022 Redistributable (x64) must be installed on every PC that runs the app. For details, see Windows runtime prerequisites.
Method summary
Every method takes CancellationToken cancellationToken = default as its last parameter. For the call conventions, see Call context.
- SaveAsync(): Save a value under a key, overwriting any value already saved under the same key
- LoadAsync(): Get the value saved under a key
- DeleteAsync(): Delete the value saved under a key
- ClearAsync(): Clear the storage by deleting all values this app has saved
If you cancel a call with a CancellationToken, it returns a Failure whose Code is Cancelled. However, a storage operation that has already started is not stopped, so a canceled save or delete may actually complete.
Common parameters
SaveAsync(), LoadAsync(), and DeleteAsync() take the request as the request parameter, and request is Required. The method descriptions below show only the request type and omit the parameter table. Check the fields of each request type in Data types.
Exceptions
ArgumentNullException: Whenrequestisnull
Result handling
DataCorrupted and AccessDenied mean different states of the stored data, so always handle them separately. DataCorrupted is the only result for which you may clear the storage.
| Result case | Stored data | App handling |
|---|---|---|
DataCorrupted | The encryption key or the stored data is corrupted, so the saved value cannot be read again. | Clear the storage with ClearAsync(), and then get the value again. If it was a credential, have the app user log in again. |
AccessDenied | The OS denied access to the storage, or another instance on Windows is using the storage; the saved value may still remain. | Do not clear the storage. Try again later, or proceed without the saved value. |
Failure | A failure for which corruption could not be confirmed. The saved value may still remain. | Do not clear the storage. Check the cause with Problem. |
UnknownOutcome | A result this SDK version does not recognize. | Do not clear the storage. Handle it as a failure. |
If you clear the storage on AccessDenied, Failure, or UnknownOutcome, even credentials that are still valid are lost. For the criteria for preserving credentials, see Preserve stored credentials.
On iOS, macOS, and Windows, DataCorrupted is returned only from LoadAsync(). On Android, it can also be returned from SaveAsync() and DeleteAsync().
Distinguish the cause of a Failure with Problem.Code.
InvalidArgument: A key that is an empty string or longer than 256 charactersFailedPrecondition: On iOS and macOS, the app has no Keychain entitlement or the provisioning profile does not matchCancelled: A call canceled with aCancellationTokenInternal: A storage operation failed because of insufficient disk space or a temporary I/O error, or the SDK could not read the native response
Methods
SaveAsync
Encrypts a value and saves it under a key. If a value is already saved under the same key, it is overwritten with the new value. An empty string is also saved as a value, and getting it returns an empty string.
- Request: SecureStorageSaveRequest
- Response: SecureStorageSaveResponse
Result cases — SecureStorageSaveResult
| Result case | Wire code | Description |
|---|---|---|
Success | — | The value was saved. |
DataCorrupted | data_corrupted | The value could not be saved because the storage's encryption key or stored data is corrupted. Returned only on Android. |
AccessDenied | access_denied | The OS denied access to the storage. The saved value may still remain. |
UnknownOutcome | UNKNOWN | A new result this SDK version does not recognize. |
Failure | FAILURE | The call could not be completed. Check the cause with the HiveError in Problem. |
LoadAsync
Decrypts and returns the value saved under a key. If no value is saved under the key, it returns Success, and Data.Value is null. A key that holds an empty string returns an empty string, so to distinguish it from a missing value, check whether the value is null.
- Request: SecureStorageLoadRequest
- Response: SecureStorageLoadResponse
Result cases — SecureStorageLoadResult
| Result case | Wire code | Description |
|---|---|---|
Success | — | The retrieval succeeded. The value is in Data.Value, and it is null if no value is saved under the key. |
DataCorrupted | data_corrupted | The saved value cannot be decrypted. |
AccessDenied | access_denied | The OS denied access to the storage. The saved value may still remain. |
UnknownOutcome | UNKNOWN | A new result this SDK version does not recognize. |
Failure | FAILURE | The call could not be completed. Check the cause with the HiveError in Problem. |
Call example
using Hive.Axyl.Storage;
var result = await storage.LoadAsync(new SecureStorageLoadRequest
{
Key = "refresh_token",
});
switch (result)
{
case SecureStorageLoadResult.Success success:
string? refreshToken = success.Data.Value; // null if no value is saved
break;
case SecureStorageLoadResult.DataCorrupted:
// The saved value cannot be read again. Clear the storage and have the user log in again.
await storage.ClearAsync();
break;
default:
// AccessDenied, Failure, UnknownOutcome: Do not clear the storage; proceed without the saved value.
break;
}
DeleteAsync
Deletes the value saved under a key. It returns Success even if no value is saved under the key.
At logout, do not clear the storage; use this method to delete only the keys you need to delete. ClearAsync() also deletes values that must be kept after logout.
- Request: SecureStorageDeleteRequest
- Response: SecureStorageDeleteResponse
Result cases — SecureStorageDeleteResult
| Result case | Wire code | Description |
|---|---|---|
Success | — | The value was deleted, or no value is saved under the key. |
DataCorrupted | data_corrupted | The value could not be deleted because the stored data is corrupted. Returned only on Android. |
AccessDenied | access_denied | The OS denied access to the storage. The saved value may still remain. |
UnknownOutcome | UNKNOWN | A new result this SDK version does not recognize. |
Failure | FAILURE | The call could not be completed. Check the cause with the HiveError in Problem. |
ClearAsync
Clears the storage by deleting all values this app has saved with ISecureStorage. Because it deletes saved values without decrypting them, it can also clear storage whose data is corrupted, and it does not return DataCorrupted as a result. On Android, it also deletes the storage's encryption key and creates a new key the next time a value is saved.
Use this method to recover the storage when you receive DataCorrupted. If you call it on other results, even values that are still valid are deleted.
- Request: None
- Response: SecureStorageClearResponse
Result cases — SecureStorageClearResult
| Result case | Wire code | Description |
|---|---|---|
Success | — | The storage was cleared. |
AccessDenied | access_denied | The values in the storage could not be deleted. Some or all of the saved values may still remain. |
UnknownOutcome | UNKNOWN | A new result this SDK version does not recognize. |
Failure | FAILURE | The call could not be completed. Check the cause with the HiveError in Problem. |
Marker interfaces
Use these to branch on the results of the four methods at once, regardless of the result type. The namespace of both interfaces is Hive.Axyl.Storage. In each result type, DataCorrupted implements IDataCorruptedOutcome, and AccessDenied implements IAccessDeniedOutcome. For the marker interfaces common to all modules, see Core result model.
IDataCorruptedOutcome
interface
Marks a result in which the saved value cannot be read again, so you must clear the storage and get the value again.
| Property | Type | Required | Description |
|---|---|---|---|
Code | string | Required | The wire code data_corrupted. |
IAccessDeniedOutcome
interface
Marks a result in which the OS denied access to the storage, so the saved value may still remain.
| Property | Type | Required | Description |
|---|---|---|---|
Code | string | Required | The wire code access_denied. |
Data types
Fields shared by several request types have the same meaning.
Key: The key that identifies a value. A string of 1 to 256 characters that the app defines
If the key is an empty string or longer than 256 characters, the call returns a Failure whose Code is InvalidArgument. On Windows, the key length is counted in UTF-8 bytes, so to use the same key on all platforms, make keys of 256 or fewer ASCII characters.
SecureStorageClearResponse
No fields.
SecureStorageDeleteRequest
A request to delete a value.
| Field | Type | Required | Description |
|---|---|---|---|
Key | string | Required | The key of the value to delete. |
SecureStorageDeleteResponse
No fields.
SecureStorageLoadRequest
A request to get a value.
| Field | Type | Required | Description |
|---|---|---|---|
Key | string | Required | The key of the value to get. |
SecureStorageLoadResponse
The result of getting a value.
| Field | Type | Required | Description |
|---|---|---|---|
Value | string? | Optional | The value saved under the key. null if no value is saved under the key. This is sensitive information, so do not write it to logs. |
SecureStorageSaveRequest
A request to save a value.
| Field | Type | Required | Description |
|---|---|---|---|
Key | string | Required | The key to save the value under. If a value is already saved under the same key, it is overwritten. |
Value | string | Required | The value to save. You can also save an empty string. It is intended for saving small values such as authentication tokens; behavior when you save large data is not guaranteed. |
SecureStorageSaveResponse
No fields.