Skip to content

Secure storage: document Android backup corruption, iOS Keychain persistence after uninstall, iCloud sync behavior, and Windows size limits #3257

@PureWeen

Description

@PureWeen

Summary

The secure storage documentation is missing several platform-specific behaviors that cause data loss, unexpected data exposure, or silent failures in production apps:

  1. Android: backup restoration corrupts secure storage — encrypted values backed up via Auto Backup cannot be decrypted on a new device (encryption keys don't transfer), causing exceptions on every GetAsync call after restore
  2. iOS: Keychain entries persist after app uninstall — unlike Android, uninstalling an iOS app does not clear its Keychain entries; values survive reinstall
  3. iOS: Keychain entries can sync via iCloud — if the user has iCloud Keychain enabled, secure storage values may silently appear on other devices
  4. Windows: 64 KB total composite storage limit — not documented; exceeding it fails silently or throws

Why it matters

Each of these causes real production incidents:

  • Android backup corruption is the most impactful: after a user restores their phone to a new device, every GetAsync call throws an exception. Apps that don't wrap secure storage in try/catch crash on launch. The fix (disabling backup or excluding the secure storage preference file) requires manifest changes that aren't mentioned in the docs.

  • iOS Keychain persistence after uninstall causes a security issue: if a user sells their device and a new user installs the same app, the new user can access the previous user's stored tokens. The fix (clearing Keychain on first launch using a UserDefaults flag) is a known pattern but not documented.

  • iOS iCloud sync means a token stored on one device can appear on another — a surprising behavior for data the developer considers device-local.

What should be documented

Android: Handle Auto Backup to prevent restore corruption

Warning: Android Auto Backup can back up and restore SecureStorage values to a new device where the original encryption key no longer exists. This causes GetAsync to throw on every call after restore. Always handle this exception and clear corrupted values:

try
{
    var value = await SecureStorage.Default.GetAsync("auth_token");
}
catch (Exception)
{
    // Encrypted value from backup cannot be decrypted — clear all secure values
    SecureStorage.Default.RemoveAll();
}

To prevent the issue, exclude SecureStorage from Auto Backup in AndroidManifest.xml:

<application android:fullBackupContent="@xml/auto_backup_rules" ...>

With Platforms/Android/Resources/xml/auto_backup_rules.xml:

<full-backup-content>
  <exclude domain="sharedpref"
           path="${applicationId}.microsoft.maui.essentials.preferences.xml" />
</full-backup-content>

iOS: Keychain does not clear on uninstall

Note: Unlike Android, uninstalling an iOS app does not remove its Keychain entries. If the app is reinstalled, previously stored values are still accessible. To avoid exposing one user's stored data to another, check for a "first launch" flag and clear Keychain entries if this is a fresh install:

if (!Preferences.Default.ContainsKey("app_initialized"))
{
    SecureStorage.Default.RemoveAll();
    Preferences.Default.Set("app_initialized", true);
}

iOS: iCloud Keychain sync

Note: If the device user has iCloud Keychain enabled, Keychain values (including those written by SecureStorage) may sync to the user's other Apple devices. This is not controllable from .NET MAUI. If device-local isolation is required, consider this behavior when deciding what data to store in SecureStorage.

Windows: storage size limits

Windows limits: SecureStorage on Windows stores values in ApplicationData. The key name maximum is 255 characters, individual values are limited to 8 KB, and the composite storage total is limited to 64 KB. Exceeding these limits throws. Store only short string values (tokens, keys) — not binary data or large serialized objects.

Suggested location

docs/platform-integration/storage/secure-storage.md — add a "Platform-specific behavior" section with per-platform callouts for each of the above

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions