Storage and data on the device

What the SDK stores on the device, where each platform keeps it, what sign-out removes and how the SDK holds its keys.

Preview

This page describes a pre-release version of the SDK. Names, versions and APIs on this page can change before the release.

The SDK stores its own data. Your app does not pass it a database, a keychain or a directory, and it does not read or write the SDK's data itself. This page describes that data for your app's security and privacy review.

Storage areas

The SDK uses four storage areas on every platform:

AreaiOSAndroidWeb
Secure storageKeychain generic-password items with service id.folio.secret_storage, accessible after first unlockEncryptedSharedPreferences file folio_secret_storage, keys encrypted with AES-256-SIV and values with AES-256-GCM, under the master key folio_master_keyIndexedDB database folio_secret_storage, each value encrypted with AES-GCM under a WebCrypto master key kept in the same database
Platform key storeP-256 keys in the Secure Enclave when the device has one, otherwise in the Keychain; accessible after first unlock, on this device onlyAndroid Keystore P-256 keys, hardware-backed when the device supports itNon-extractable WebCrypto P-256 keys in the IndexedDB database folio_key_store
Key-value storageUserDefaults.standard, keys prefixed folio.storage.SharedPreferences file folio_storage, keys prefixed folio.storage.localStorage, keys prefixed folio_storage:
FilesThe app's Documents directoryThe app's filesDirThe Origin Private File System, directory documents

On Android the master key of the secure storage uses the AES-256-GCM key scheme and is hardware-backed when the device supports it. On the web the master key is generated as a non-extractable key. When the browser cannot store a non-extractable key in IndexedDB, the SDK stores the raw key bytes in the same database and imports them as non-extractable on each load.

Scopes

Every stored value belongs to one of three scopes. The scope is the prefix of the stored key or the file path.

ScopePrefixHolds
Globalfolio/globalFacts about the installation that outlive any session
Devicefolio/defaultThe device session and device settings
Accountfolio/accounts/<account id>Data of one signed-in account; one scope per account

What the SDK stores

DataScopeArea
The committed identity: guest or signed-in account, and its account idGlobalKey-value storage
The device id the server assignedGlobalKey-value storage
The session tokensDeviceSecure storage
The first renewal request of a session, until Folio has answered itDevice or accountSecure storage
The device's session key (DPoP key)DevicePlatform key store
How often the session key was created, and when it was last createdDeviceKey-value storage
The platform attestation stateDeviceSecure storage
The guests whose vault records still have to move to an accountDeviceKey-value storage
The language picked in the inquiryDeviceKey-value storage
The current inquiry id and the state needed to resume that inquiryDeviceKey-value storage
The account session and its session contextAccountSecure storage
The account keys, stored wrapped, and account credential secretsAccountSecure storage
The list of account keys staged by a sign-in that has not finishedAccountKey-value storage
The vault records the device has loaded or createdAccountFiles
Vault attachments, stored encryptedAccountFiles
The vault sync positionAccountKey-value storage
The progress of moving a guest's vault records into this accountAccountKey-value storage
The index of saved identities and verificationsAccountKey-value storage
The account's notificationsAccountFiles
The notification sync positionAccountKey-value storage
The offline session audit logAccountKey-value storage
The time of the last server contactDeviceKey-value storage
The log journal, when the logging configuration has a file writerNoneFiles: folio/logs; see Log files

The platform attestation state also records a rejection by Folio: after one, the SDK does not attest the platform again for 24 hours, unless the Folio realm, your application's version or build, or the SDK version changes; then it attests again at the next start.

A stored committed identity, session, session context or platform attestation state that cannot be decoded is removed, not replaced silently. With logging on, the SDK reports the removal as a stored state purged warning. See Logging. Vault records that cannot be read are not removed: the vault's sync phase becomes Error; see Sync.

Log files

With a file writer in FolioSdkConfig.logging, as FolioSdk.loggingConfig sets it up, the SDK writes its log journal to the directory folio/logs of the files area: the app's Documents directory on iOS, its filesDir on Android and the origin private file system on the web. A LoggingConfig without a file writer writes no files.

Path in folio/logsHolds
<time>.<runId>.<seq>.jsonlEntries of one runtime as JSON lines, one entry per line. <time> is the time of the first entry in milliseconds since the Unix epoch, <seq> its sequence number, both padded to 20 digits, and <runId> the runtime.
exports/<export id>/The last export: the exported entries as .jsonl files, the same entries as readable .log files, and manifest.json.
  • One directory for the installation. The log files belong to no scope. Entries of the guest and of every account that signs in on the device go into the same files; each entry names its Folio session.
  • A new file starts with each runtime, when the current file would grow past segmentBytes, and when the first entry of the current file is older than retentionMillis.
  • Retention. Before it writes an entry, the SDK deletes, oldest first, the files whose first entry is older than retentionMillis and the files that would make the journal exceed totalBytes: seven days and 64 MiB by default. See Configure the writers. The SDK deletes files only when it writes an entry, so the files stay on the device while no runtime writes.
  • Exports. Export of LogsStore removes the previous export, then writes the new one under exports. An export stays on the device until the next export; the journal's limits neither count nor remove it.
  • Sign-out removes no log file and no export.
  • No other files. Any other file in folio/logs makes loading, writing and exporting the journal fail with the LogError Corrupt. Do not put your own files there.

What sign-out removes

Sign-out is SessionAction.SignOut on the session store: .signOut in Swift, SessionAction.SignOut in Kotlin and SessionAction.signOut in TypeScript (see Session). It:

  1. Stops the account's running services.
  2. Asks the server to revoke the session. If the request fails, sign-out continues on the device.
  3. Removes the session tokens from memory and from secure storage, and drops pending sign-in challenges held in memory.
  4. Removes the account's session and session context from secure storage.
  5. Clears the committed identity.
  6. Starts a new guest session.

Sign-out does not remove the other account-scoped data in the table above, such as the vault files, the wrapped account keys and the identity index, and it does not remove device-scoped settings such as the inquiry language pick. When the account signs in again on the same device, the SDK uses the same account scope.

Sign-out does not remove the log files or the last log export either; see Log files.

When a guest signs in to an account, the SDK first moves the guest's vault records into the account. It keeps the guest's scope until that move has finished, and then removes it. See Guests and accounts.

Key custody

The SDK generates and holds its own keys. Your app never receives key material, and no SDK function returns it.

  • Generated on the device. Keys are generated on the device with the platform's secure random source: AES-256 keys, and EC key pairs on the P-256 and P-384 curves.
  • Stored in secure storage. A persistent key is stored as one record in secure storage, in the scope it belongs to. A volatile key is kept in memory only and is gone when the runtime stops. Secret key bytes in memory are wiped when they are released. The session key is the exception; see below.
  • Restricted use. Each key records what it may be used for: signing, encryption, decryption, or wrapping and unwrapping other keys. A key cannot be used for anything else. A key that wraps other keys cannot sign, encrypt or decrypt data.
  • No plaintext export. A key is either non-transferable, and then it never leaves the custody, or transferable, and then it leaves only wrapped: encrypted under another stored key or under a recipient's public key. A key that wraps other keys is always non-transferable.
  • Device-bound session. The session is bound to a P-256 key that the SDK creates in the platform key store. The private key never leaves the key store and cannot be transferred; the SDK asks the key store for an ES256 signature over a proof for its requests, so the session tokens alone do not authorize a request from another device. The SDK refuses a key that the key store protects less than the platform requires: on iOS, Android and the web the key must be held in secure hardware or by the operating system's key store, not in software storage or memory.
  • Per-record vault keys. Each vault record has its own random record key. The SDK wraps the record key with the account's public key. A record in a folder also has its key wrapped under the folder's key in the folder entry. An invite carries the key sealed under the invite secret until the recipient wraps it with its own account key.

The secure storage of each platform, described in Storage areas, protects the stored key records. Except for the session key, the keys are generated and used by the SDK in the app process; they are not keys created inside a hardware security module. The session key is created and used inside the platform key store, which on iOS and Android uses the device's secure hardware when it has it.

On this page