Orchestration SDKs

Customizing Journey module storage on iOS

PingOne Advanced Identity Cloud PingAM iOS

Depending on the authentication use case, you may need to store and retrieve ID tokens, access tokens, refresh tokens, or cookies.

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

The Orchestration SDKs 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.

Adding iOS dependencies

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

Add dependencies using CocoaPods

  1. If you do not already have CocoaPods, install the latest version.

  2. If you do not already have a Podfile, in a terminal window, run the following command to create a new Podfile:

    pod init
  3. Add the following lines to your 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 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:

    // Import the Storage library
    import Storage

Using the provided storage solutions

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

Provided default storage solutions

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

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.

This storage solution does not encrypt the data by default.

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:

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.

Default value

CacheStrategy.NO_CACHE

  • MemoryStorage

  • KeychainStorage

encryptor

Enable encryption of the data, by specifying the encryptor to use.

Available options are:

  • SecuredKeyEncryptor()

  • NoEncryptor()

  • MemoryStorage

  • KeychainStorage

The following code shows an example of customizing storage solutions and using custom types:

Customizing the KeychainStorage storage solution
// Define the custom data to store
struct Dog: Codable {
    let name: String
    let breed: String
}

// Create custom storage for custom data
let customStorage = KeychainStorage<Dog>(
  account: "myStorageId"
)

// Persist custom data
try? await customStorage.save(item: Dog(name: "Lucky", breed: "Golden Retriever"))

// Retrieve custom data
let storedData = try? await customStorage.get()

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.

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

Creating a storage instance with cache enabled
let customStorage = 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
let customStorage = KeychainStorage<Dog>(
  account: "myStorageId",
  encryptor: SecuredKeyEncryptor() ?? NoEncryptor()
)

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

You can create your own custom encryptor by implementing the Encryptor protocol:

Creating a custom encryptor for a storage instance
struct MyEncryptor: Encryptor {
  func encrypt(data: Data) async throws -> Data {
    // Implement the encryption logic
  }

  func decrypt(data: Data) async throws -> Data {
    // Implement the decryption logic
  }
}

Implementing your own custom storage

You can create your own custom storage solutions by implementing the Storage interface:

The Storage interface on iOS
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)
  }
}

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.

Use your custom storage solution in a module as follows:

Using a custom storage solution
let config = OathConfiguration.build { config in
  config.storage = myCustomStorage()
  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.