maui-secure-storage

📁 davidortinau/maui-skills 📅 11 days ago
4
总安装量
3
周安装量
#49066
全站排名
安装命令
npx skills add https://github.com/davidortinau/maui-skills --skill maui-secure-storage

Agent 安装分布

amp 3
gemini-cli 3
claude-code 3
github-copilot 3
codex 3
kimi-cli 3

Skill 文档

Secure Storage in .NET MAUI

API Surface

Use SecureStorage.Default (implements ISecureStorage):

// Store
await SecureStorage.Default.SetAsync("auth_token", token);

// Retrieve (returns null if not found)
string? token = await SecureStorage.Default.GetAsync("auth_token");

// Remove single key
bool removed = SecureStorage.Default.Remove("auth_token");

// Remove all
SecureStorage.Default.RemoveAll();

All values are strings only. Serialize complex data to JSON first.

Platform Setup

Android — Handle Auto Backup

Auto Backup can restore encrypted preferences to a new device where the encryption key is invalid, causing unrecoverable exceptions. Choose one approach:

Option A — Disable Auto Backup entirely:

In Platforms/Android/AndroidManifest.xml:

<application android:allowBackup="false" ...>

Option B — Exclude secure storage from backup:

  1. Create Platforms/Android/Resources/xml/auto_backup_rules.xml:
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
  <exclude domain="sharedpref"
           path="${applicationId}.microsoft.maui.essentials.preferences.xml" />
</full-backup-content>
  1. Reference it in AndroidManifest.xml:
<application android:fullBackupContent="@xml/auto_backup_rules" ...>

iOS / Mac Catalyst — Enable Keychain

In Platforms/iOS/Entitlements.plist (and Platforms/MacCatalyst/Entitlements.plist):

<dict>
  <key>keychain-access-groups</key>
  <array>
    <string>$(AppIdentifierPrefix)com.yourcompany.yourapp</string>
  </array>
</dict>

Simulator only: Add the keychain access group matching your bundle ID. Remove it before building for physical devices or App Store submission — it is not needed there and can cause signing issues.

Windows

No setup required. Limits:

  • Key name: max 255 characters
  • Value: max 8 KB per setting
  • Composite storage: max 64 KB total

Gotchas and Best Practices

  1. Small text only. Do not store large blobs. Store tokens, passwords, short secrets.
  2. Wrap in try/catch. On Android, corrupted values from backup restoration throw exceptions. Always handle failures gracefully:
    try
    {
        var value = await SecureStorage.Default.GetAsync("key");
    }
    catch (Exception)
    {
        SecureStorage.Default.RemoveAll();
    }
    
  3. iOS iCloud Keychain sync. Values may sync across devices via iCloud Keychain if the user has it enabled. This is platform behavior, not controllable from MAUI.
  4. iOS uninstall does not clear Keychain. Unlike Android, uninstalling an iOS app does not remove its Keychain entries. Values persist and are available if the app is reinstalled.
  5. No sensitive logging. Never log secret values retrieved from secure storage.

DI Wrapper Service for Testability

Define the interface

public interface ISecureStorageService
{
    Task SetAsync(string key, string value);
    Task<string?> GetAsync(string key);
    bool Remove(string key);
    void RemoveAll();
}

Implement against SecureStorage.Default

public class SecureStorageService : ISecureStorageService
{
    public Task SetAsync(string key, string value)
        => SecureStorage.Default.SetAsync(key, value);

    public async Task<string?> GetAsync(string key)
    {
        try
        {
            return await SecureStorage.Default.GetAsync(key);
        }
        catch (Exception)
        {
            // Corrupted value — clear and return null
            SecureStorage.Default.RemoveAll();
            return null;
        }
    }

    public bool Remove(string key)
        => SecureStorage.Default.Remove(key);

    public void RemoveAll()
        => SecureStorage.Default.RemoveAll();
}

Register in MauiProgram.cs

builder.Services.AddSingleton<ISecureStorageService, SecureStorageService>();

Inject into view models

public class LoginViewModel
{
    private readonly ISecureStorageService _secure;

    public LoginViewModel(ISecureStorageService secure)
    {
        _secure = secure;
    }

    public async Task SaveTokenAsync(string token)
    {
        await _secure.SetAsync("auth_token", token);
    }

    public async Task<string?> GetTokenAsync()
    {
        return await _secure.GetAsync("auth_token");
    }
}

Mock in tests

var mock = new Mock<ISecureStorageService>();
mock.Setup(s => s.GetAsync("auth_token"))
    .ReturnsAsync("test-token-value");

var vm = new LoginViewModel(mock.Object);