> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Session storage

# Session Storage

This guide covers securely storing and restoring Privacy Boost sessions using iOS Keychain, expanding on the persistence options in [Key Management](/sdk/concepts/key-management#persistence--protection).

## Why Keychain?

Session data includes sensitive cryptographic keys. Keychain provides:

* Hardware-backed encryption
* Access control (biometrics, passcode)
* Secure enclave support
* Data protection across app updates

## Basic Keychain Wrapper

Create a Keychain helper:

```swift theme={null}
import Foundation
import Security

enum KeychainError: Error {
    case duplicateItem
    case itemNotFound
    case unexpectedStatus(OSStatus)
    case invalidData
}

class KeychainManager {
    static let shared = KeychainManager()

    private let service = "com.yourapp.privacyboost"

    private init() {}

    // MARK: - Save

    func save(_ data: Data, for key: String) throws {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key,
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]

        let status = SecItemAdd(query as CFDictionary, nil)

        if status == errSecDuplicateItem {
            // Update existing item
            let updateQuery: [String: Any] = [
                kSecClass as String: kSecClassGenericPassword,
                kSecAttrService as String: service,
                kSecAttrAccount as String: key
            ]

            let attributes: [String: Any] = [
                kSecValueData as String: data
            ]

            let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributes as CFDictionary)
            guard updateStatus == errSecSuccess else {
                throw KeychainError.unexpectedStatus(updateStatus)
            }
        } else if status != errSecSuccess {
            throw KeychainError.unexpectedStatus(status)
        }
    }

    // MARK: - Load

    func load(for key: String) throws -> Data {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]

        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)

        guard status == errSecSuccess else {
            if status == errSecItemNotFound {
                throw KeychainError.itemNotFound
            }
            throw KeychainError.unexpectedStatus(status)
        }

        guard let data = result as? Data else {
            throw KeychainError.invalidData
        }

        return data
    }

    // MARK: - Delete

    func delete(for key: String) throws {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key
        ]

        let status = SecItemDelete(query as CFDictionary)
        guard status == errSecSuccess || status == errSecItemNotFound else {
            throw KeychainError.unexpectedStatus(status)
        }
    }

    // MARK: - Exists

    func exists(for key: String) -> Bool {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key,
            kSecReturnData as String: false
        ]

        let status = SecItemCopyMatching(query as CFDictionary, nil)
        return status == errSecSuccess
    }
}
```

## Session Storage

Store and retrieve Privacy Boost sessions:

```swift theme={null}
import PrivacyBoost

class SessionStorage {
    static let shared = SessionStorage()

    private let keychain = KeychainManager.shared
    private let sessionKey = "privacyboost.session"

    private init() {}

    // MARK: - Save Session

    func saveSession(_ session: ExportedSession) throws {
        let data = try encodeSession(session)
        try keychain.save(data, for: sessionKey)
    }

    // MARK: - Load Session

    func loadSession() throws -> ExportedSession {
        let data = try keychain.load(for: sessionKey)
        return try decodeSession(data)
    }

    // MARK: - Delete Session

    func deleteSession() throws {
        try keychain.delete(for: sessionKey)
    }

    // MARK: - Has Session

    func hasSession() -> Bool {
        return keychain.exists(for: sessionKey)
    }

    // MARK: - Encoding

    private func encodeSession(_ session: ExportedSession) throws -> Data {
        let dict: [String: Any] = [
            "version": session.version,
            "data": session.data
        ]

        return try JSONSerialization.data(withJSONObject: dict)
    }

    private func decodeSession(_ data: Data) throws -> ExportedSession {
        guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            throw KeychainError.invalidData
        }

        return ExportedSession(
            version: dict["version"] as? UInt32 ?? 1,
            data: dict["data"] as? String ?? ""
        )
    }
}
```

## Biometric Protection

Add Face ID/Touch ID protection:

```swift theme={null}
import LocalAuthentication

class BiometricSessionStorage {
    static let shared = BiometricSessionStorage()

    private let keychain = KeychainManager.shared
    private let sessionKey = "privacyboost.session.biometric"

    private init() {}

    // MARK: - Save with Biometrics

    func saveSession(_ session: ExportedSession) throws {
        let data = try encodeSession(session)

        // Create access control with biometrics
        var error: Unmanaged<CFError>?
        guard let accessControl = SecAccessControlCreateWithFlags(
            kCFAllocatorDefault,
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
            .biometryCurrentSet,
            &error
        ) else {
            throw error!.takeRetainedValue() as Error
        }

        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: "com.yourapp.privacyboost.biometric",
            kSecAttrAccount as String: sessionKey,
            kSecValueData as String: data,
            kSecAttrAccessControl as String: accessControl
        ]

        // Delete existing and add new
        SecItemDelete(query as CFDictionary)
        let status = SecItemAdd(query as CFDictionary, nil)

        guard status == errSecSuccess else {
            throw KeychainError.unexpectedStatus(status)
        }
    }

    // MARK: - Load with Biometrics

    func loadSession() async throws -> ExportedSession {
        return try await withCheckedThrowingContinuation { continuation in
            let context = LAContext()
            context.localizedReason = "Access your Privacy Boost session"

            let query: [String: Any] = [
                kSecClass as String: kSecClassGenericPassword,
                kSecAttrService as String: "com.yourapp.privacyboost.biometric",
                kSecAttrAccount as String: sessionKey,
                kSecReturnData as String: true,
                kSecUseAuthenticationContext as String: context
            ]

            DispatchQueue.global().async {
                var result: AnyObject?
                let status = SecItemCopyMatching(query as CFDictionary, &result)

                if status == errSecSuccess, let data = result as? Data {
                    do {
                        let session = try self.decodeSession(data)
                        continuation.resume(returning: session)
                    } catch {
                        continuation.resume(throwing: error)
                    }
                } else {
                    continuation.resume(throwing: KeychainError.unexpectedStatus(status))
                }
            }
        }
    }

    // MARK: - Check Biometrics Available

    static var biometricsAvailable: Bool {
        let context = LAContext()
        var error: NSError?
        return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
    }

    static var biometricType: String {
        let context = LAContext()
        _ = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)

        switch context.biometryType {
        case .faceID:
            return "Face ID"
        case .touchID:
            return "Touch ID"
        case .opticID:
            return "Optic ID"
        default:
            return "Biometrics"
        }
    }

    // MARK: - Encoding (same as SessionStorage)

    private func encodeSession(_ session: ExportedSession) throws -> Data {
        let dict: [String: Any] = [
            "version": session.version,
            "data": session.data
        ]
        return try JSONSerialization.data(withJSONObject: dict)
    }

    private func decodeSession(_ data: Data) throws -> ExportedSession {
        guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            throw KeychainError.invalidData
        }

        return ExportedSession(
            version: dict["version"] as? UInt32 ?? 1,
            data: dict["data"] as? String ?? ""
        )
    }
}
```

## Usage in App

Integrate with your SDK manager:

```swift theme={null}
actor SDKManager {
    // ... existing code ...

    func restoreSession() async throws -> Bool {
        // Try biometric first
        if BiometricSessionStorage.biometricsAvailable {
            do {
                let session = try await BiometricSessionStorage.shared.loadSession()
                return try sdk.importSession(session)
            } catch {
                // Fall through to regular storage
            }
        }

        // Try regular storage
        if SessionStorage.shared.hasSession() {
            let session = try SessionStorage.shared.loadSession()
            return try sdk.importSession(session)
        }

        return false
    }

    func saveCurrentSession(withBiometrics: Bool = false) throws {
        guard let session = sdk.exportSession() else {
            return
        }

        if withBiometrics && BiometricSessionStorage.biometricsAvailable {
            try BiometricSessionStorage.shared.saveSession(session)
        } else {
            try SessionStorage.shared.saveSession(session)
        }
    }

    func clearSession() throws {
        try SessionStorage.shared.deleteSession()
    }
}
```

## Security Best Practices

1. **Use `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`** - Data only accessible when device is unlocked
2. **Enable biometrics** - Add Face ID/Touch ID for sensitive operations
3. **Don't store in UserDefaults** - UserDefaults is not encrypted
4. **Clear on logout** - Delete Keychain items when user logs out
5. **Handle errors gracefully** - Don't expose Keychain errors to users

## Info.plist

Add Face ID usage description:

```xml theme={null}
<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to secure your Privacy Boost session</string>
```

## Next Steps

* [API Reference](../api-reference) - Complete API documentation
* [iOS Getting Started](../getting-started) - Integrate with SwiftUI
