Skip to content

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 AccessDenied or a Failure whose Code is Internal.
  • 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: When request is null

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 characters
  • FailedPrecondition: On iOS and macOS, the app has no Keychain entitlement or the provisioning profile does not match
  • Cancelled: A call canceled with a CancellationToken
  • Internal: 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.

Task<SecureStorageSaveResult> SaveAsync(SecureStorageSaveRequest request, CancellationToken cancellationToken = default)

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.

Task<SecureStorageLoadResult> LoadAsync(SecureStorageLoadRequest request, CancellationToken cancellationToken = default)

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.

Task<SecureStorageDeleteResult> DeleteAsync(SecureStorageDeleteRequest request, CancellationToken cancellationToken = default)

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.

Task<SecureStorageClearResult> ClearAsync(CancellationToken cancellationToken = default)

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.
using Hive.Axyl.Storage;

if (result is IDataCorruptedOutcome)
{
    // Clear the storage, and then get the value again.
}
else if (result is IAccessDeniedOutcome)
{
    // Do not clear the storage; try again later.
}

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.