---
title: Customize storage
description: Applies to:
component: sdks
version: latest
page_id: sdks:davinci:customize/how-to-customize-storage
canonical_url: https://docs.pingidentity.com/sdks/latest/davinci/customize/how-to-customize-storage.html
revdate: Mon, 2 September 2024 14:23:20 +0100
keywords: ["DaVinci Client", "Flows", "Setup &amp; Configuration", "Source Code", "Customization", "SDK"]
section_ids:
  add_dependencies: Add dependencies
  install-protect-android: Add Android dependencies
  install-protect-ios: Add iOS dependencies
  add_dependencies_using_cocoapods: Add dependencies using CocoaPods
  add_dependencies_using_swift_package_manager: Add dependencies using Swift Package Manager
  using_the_provided_storage_solutions: Using the provided storage solutions
  provided_default_storage_solutions: Provided default storage solutions
  creating_a_storage_instance: Creating a storage instance
  configuring_storage_solutions: Configuring storage solutions
  caching: Enabling caching
  encrypt-ios: Encrypting storage instances on iOS
  implementing_your_own_custom_storage: Implementing your own custom storage
---

# Customize storage

***Applies to***:

* [icon: check-square-o, set=fa]DaVinci client for Android

* [icon: check-square-o, set=fa]DaVinci client for iOS

* [icon: square-o, set=fa]DaVinci client for JavaScript

Depending on the authentication use case, the DaVinci client may need to store and retrieve ID tokens, access tokens, and refresh tokens.

Each token is serving a different use case, and as such how the DaVinci clients handle them can be different.

The DaVinci clients employ identity best practices for storing data by default. However there are use cases where you might need to customize how the SDK stores data.

For example, you might be running on hardware that provides specialized security features, or perhaps target older hardware that cannot handle the latest algorithms.

For these cases, you can customize the provided storage solutions, or provide your own custom storage classes.

## Add dependencies

To customize your storage solution you need to add the `storage` module to your project.

### Add Android dependencies

To add the PingOne Protect dependencies to your Android project:

1. In the **Project** tree view of your Android Studio project, open the `Gradle Scripts/build.gradle.kts` file for the *module*.

2. In the `dependencies` section, add the required dependencies:

   Example `dependencies` section after editing `build.gradle.kts`:

   ```gradle
   dependencies {
     // DaVinci Client main module
     implementation("com.pingidentity.sdks:davinci:1.3.0")

     // Storage module
     implementation("com.pingidentity.sdks:storage:1.3.0")
   }
   ```

### Add iOS dependencies

You can use CocoaPods or the Swift Package Manager to add the PingOne Protect dependencies to your iOS project.

#### Add dependencies using CocoaPods

1. If you do not already have CocoaPods, install the [latest version](https://guides.cocoapods.org/using/getting-started.html).

2. If you do not already have a Podfile, in a terminal window, run the following command to create a new [Podfile](https://guides.cocoapods.org/syntax/podfile.html):

   ```podfile
   pod init
   ```

3. Add the following lines to your Podfile:

   ```podfile
   pod 'Storage' // Add-on for customizing storage
   ```

4. Run the following command to install pods:

   ```
   pod install
   ```

#### Add dependencies using Swift Package Manager

1. With your project open in **Xcode**, select File > Add Package Dependencies.

2. In the search bar, enter the DaVinci Client for iOS repository URL: `https://github.com/ForgeRock/ping-ios-sdk`.

3. Select the `ping-ios-sdk` package, and then click Add Package.

4. In the Choose Package Products dialog, ensure that the `Storage` library is added to your target project.

5. Click Add Package.

6. In your project, import the library:

   ```swift
   // Import the Storage library
   import Storage
   ```

## Using the provided storage solutions

You can use the default storage solutions included in the DaVinci client, and configure them to suit your requirements.

### Provided default storage solutions

The DaVinci client includes default storage solutions you can use in your apps, depending on the type of data you want to store.

* Android

* iOS

- `MemoryStorage`

  Storage that stores data in memory.

  |   |                                                                                                                                                                                                 |
  | - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  |   | Data stored using the `MemoryStorage` solution is kept in plain text and is not encrypted.A device that can output a memory dump may expose sensitive information, such as access or ID tokens. |

- `DataStoreStorage`

  Storage backed by [Jetpack DataStore](https://developer.android.com/topic/libraries/architecture/datastore).

  |   |                                                                                               |
  | - | --------------------------------------------------------------------------------------------- |
  |   | Data stored using the `DataStoreStorage` solution is kept in plain text and is not encrypted. |

- `EncryptedDataStoreStorage`

  Encrypted version of the `DataStoreStorage` solution, also backed by [Jetpack DataStore](https://developer.android.com/topic/libraries/architecture/datastore).

  |   |                                              |
  | - | -------------------------------------------- |
  |   | All SDK modules use this solution by default |

* `MemoryStorage`

  Storage that stores data in memory.

  |   |                                                                                                                                                                                                 |
  | - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  |   | Data stored using the `MemoryStorage` solution is kept in plain text and is not encrypted.A device that can output a memory dump may expose sensitive information, such as access or ID tokens. |

* `KeychainStorage`

  Storage backed by the [iOS keychain](https://support.apple.com/en-gb/guide/security/secb0694df1a/web).

  This storage solution does not encrypt the data by default.

### Creating a storage instance

Use the following code to create a storage instance, add any additional configuration, and store and retrieve data:

* Android

* iOS

Using the `EncryptedDataStoreStorage` storage solution

```kotlin
@Serializable
data class Dog(val name: String, val type: String)

val storage = EncryptedDataStoreStorage<String> {
    fileName = "com.example.safe"
    keyAlias = "com.example.v1.KEYS"
}
storage.save(Dog("Lucky", "Golden Retriever"))
val storedData = storage.get()
```

Using the `KeychainStorage` storage solution

```swift
// Define the data type that you want to persist
struct Dog: Codable {
    let name: String
    let type: String
}

let storage = KeychainStorage<Dog>(account: "myStorageId") // Create the storage

try? await storage.save(item: Dog(name: "Lucky", type: "Golden Retriever")) // Persist the item
let storedData = try? await storage.get() // Retrieve the item
```

### Configuring storage solutions

You can customize aspects of the storage solutions by passing parameters when creating a storage instance.

The available properties are listed below:

* Android

* iOS

On Android, you can configure the `EncryptedDataStoreStorage` storage solution with the following properties:

**Android EncryptedDataStoreStorage properties**

| Property             | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cacheStrategy`      | Enable caching of the data.Learn more in [Enabling caching](#caching).- Default value

  `CacheStrategy.NO_CACHE`                                                                                                                                                                                                                                                                                                                                                                                                       |
| `fileName`           | The name of the file used for persistent storage.- Default values

  * OpenID Connect token storage

    `com.pingidentity.sdk.v1.tokens`

  * Cookie storage

    `com.pingidentity.sdk.v1.cookies`                                                                                                                                                                                                                                                                                                                    |
| `keyAlias`           | The string used as the alias for the key the DaVinci client to use. When provided, the DaVinci client enables encryption using `AndroidKeyStore`.You can use any value that does not clash with any other key names. A common pattern is `<top-level-domain>.<company-name>.<version>.KEYS`.For example, `com.example.v1.KEYS`.- Default values

  * OpenID Connect token storage

    `com.pingidentity.sdk.v1.tokens`

  * Cookie storage

    `com.pingidentity.sdk.v1.cookies`                                      |
| `strongBoxPreferred` | When `true` the DaVinci client attempts to use hardware-backed [StrongBox](https://developer.android.com/privacy-and-security/keystore#StrongBoxKeyMint) functionality for key storage, if available on the client device.Some devices implement StrongBox, but are not optimal. You can use the [`Build`](https://developer.android.com/reference/android/os/Build) class to conditionally apply the `strongBoxPreferred` flag based on the device manufacturer, model, or other properties.- Default value

  `false` |

**iOS storage properties**

| Property    | Description                                                                                                                                                                                            | Storage types                          |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- |
| `account`   | A user-defined string to uniquely identify the storage instance.                                                                                                                                       | * `MemoryStorage`

* `KeychainStorage` |
| `cacheable` | Enable caching of the data.Learn more in [Enabling caching](#caching).* Default value

  `CacheStrategy.NO_CACHE`                                                                                      | - `MemoryStorage`

- `KeychainStorage` |
| `encryptor` | Enable encryption of the data, by specifying the encryptor to use.Learn more in [Encrypting storage instances on iOS](#encrypt-ios).Available options are:- `SecuredKeyEncryptor()`

- `NoEncryptor()` | * `MemoryStorage`

* `KeychainStorage` |

The following code shows examples of customizing storage solutions:

* Android

* iOS

Customizing the `EncryptedDataStoreStorage` storage solution

```kotlin
module(Oidc) {
    clientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
    discoveryEndpoint = "https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"

    // OpenID Connect storage configuration options
    storage {
        fileName = "myOidcTokens"
        keyAlias = "com.example.v1.KEYS"
        strongBoxPreferred = true
        cacheStrategy = CacheStrategy.CACHE_ON_FAILURE
    }
}
module(Cookie) {
    // The cookie name to persist
    persist = mutableListOf("ST", "ST-NO-SS")
    // Cookie storage configuration options
    storage {
        strongBoxPreferred = false
    }
}
```

Customizing the `KeychainStorage` storage solution

```swift
let storage = KeychainStorage<DataObj>(
  account: "myStorageId",
  encryptor: SecuredKeyEncryptor() ?? NoEncryptor(),
  cacheable: true
)
```

#### Enabling caching

You can add caching to each storage solution depending on the requirements of the type of data you store.

|   |                                                                                                                                                                         |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Data stored in a cache is kept in plain text and is not encrypted.A device that can output a memory dump may expose sensitive information, such as access or ID tokens. |

* Android

* iOS

Use the `cacheStrategy` property when creating a storage instance to configure the type of cache the storage uses. The available options are as follows:

* `CacheStrategy.NO_CACHE`

  The default for each storage solution.

  Prevents caching and always fetches data from storage.

  Use for critical data that must always be up-to-date.

* `CacheStrategy.CACHE_ON_FAILURE`

  Caches data in memory if the storage operation fails.

  Use to overcome storage interruptions, and allow fallback data reads.

* `CacheStrategy.CACHE`

  Always caches data in memory.

  Use for non-critical, but highly performant data reads.

**Example**:

Creating a storage instance with cache enabled

```kotlin
val storage = DataStoreStorage<String> {
    fileName = "com.pingidentity.sdk.v1.tokens"
    cacheStrategy = CacheStrategy.CACHE
}
```

Use the `cacheable` property when creating a storage instance to enable caching:

Creating a storage instance with cache enabled

```swift
let storage = KeychainStorage<Dog>(
  account: "myStorageId",
  cacheable: true
)
```

#### Encrypting storage instances on iOS

On iOS, you can enable encryption for any storage instance that implements the `StorageDelegate` protocol, including all the built-in storage solutions.

Creating a storage instance that uses `SecuredKeyEncryptor`

```swift
let storage = KeychainStorage<Dog>(
  account: "myStorageId",
  encryptor: SecuredKeyEncryptor() ?? NoEncryptor()
)
```

The `KeychainStorage` uses the `NoEncryptor` encryptor by default or if not specified.

## Implementing your own custom storage

You can create your own custom storage solutions by implementing the `Storage` interface. For example, you could implement a file-based or cloud-based storage solution.

You must implement the following functions in each storage class:

* `save()`

  Stores an item in the customized storage.

* `get()`

  Retrieves an item from the customized storage.

* `delete()`

  Removes an item from the customized storage.

- Android

- iOS

The `Storage` interface on Android

```kotlin
class Memory<T : @Serializable Any> : Storage<T> {
  private var data: T? = null

  override suspend fun save(item: T?) {
    data = item
  }

  override suspend fun get(): T? = data

  override suspend fun delete() {
    data = null
  }
}

// Delegate the MemoryStorage to the Storage
inline fun <reified T : @Serializable Any> MemoryStorage(): Storage<T> = StorageDelegate(Memory())
```

The `Storage` interface on iOS

```swift
public class CustomStorage<T: Codable>: Storage {
  private var data: T?

  public func save(item: T) async throws {
    data = item
  }

  public func get() async throws → T?  {
    return data
  }

  public func delete() async throws {
    data = nil
  }

}

public class CustomStorageDelegate<T: Codable>: StorageDelegate<T> {
  public init(cacheable: Bool = false) {
    super.init(delegate: CustomStorage<T>(), cacheable: cacheable)
  }
}
```

Use your custom storage solution in a module as follows:

* Android

* iOS

Using a custom storage solution

```kotlin
module(Cookie) {
    persist = mutableListOf("ST", "ST-NO-SS")
    storage = { MemoryStorage() }
}
```

Using a custom storage solution

```swift
let config = OathConfiguration.build { config in
config.storage = OathKeychainStorage()
config.enableCredentialCache = false
config.logger = customLogger
}
```

|   |                                                                                                                                                                                                                         |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Use an equals sign when assigning a custom class to the `storage` property in the module configuration.You do not need an equals sign when passing configuration settings to the default storage solution for a module. |
