mcpField
M
maui-secure-storage

maui-secure-storage

github.com/davidortinauセキュリティ17653 回インストール16 週間前に更新
リポジトリ

maui-secure-storage について

.NET MAUI向けに、Android、iOS、Windowsでのセキュアストレージの注意点と実践方法を解説するスキルです。機密情報を安全に扱い、プラットフォームの挙動を考慮し、依存性注入でストレージアクセスをテスト可能にします。

主な機能

  1. 01プラットフォーム別のストレージ注意点
  2. 02Androidのバックアップと例外処理
  3. 03iOS Keychainの挙動ガイド
  4. 04Windowsのストレージ制限
  5. 05依存性注入によるテストパターン
  6. 06機密情報の取り扱いチェックリスト

活用シーン

  1. 01MAUIアプリでトークンやパスワードを保存
  2. 02Androidのバックアップデータによる読み込み失敗を防止
  3. 03再インストール後も残るKeychainデータを考慮
  4. 04ViewModelのストレージアクセスをモック可能にする

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/davidortinau/maui-skills --skill maui-secure-storage

インストール後、Claude Code で/maui-secure-storageを入力して読み込まれたか確認します。

同梱ファイルreferences/secure-storage-api.md

詳細

セキュアストレージ — 注意点とベストプラクティス

プラットフォームの重大な落とし穴

⚠️ Android:自動バックアップで暗号化された値が壊れる

自動バックアップでは、暗号化キーが無効な新しいデバイスに暗号化された設定が復元されるため、回復不能な例外が発生します。自動バックアップを無効にするか、セキュアストレージのファイルをバックアップ対象から除外する必要があります。設定方法については、references/secure-storage-api.md を参照してください。

⚠️ Android:必ず try/catch で囲む

バックアップから復元した値が破損していると、例外が発生します。保護せずに GetAsync を呼び出さないでください。

// ❌ Unprotected — crashes on corrupted backup data
var value = await SecureStorage.Default.GetAsync("key");

// ✅ Protected — handles corruption gracefully
try
{
    var value = await SecureStorage.Default.GetAsync("key");
}
catch (Exception)
{
    SecureStorage.Default.RemoveAll();
}
⚠️ iOS:シミュレーターでのキーチェーンエンタイトルメント

シミュレーター用ビルドではキーチェーンアクセスグループを追加しますが、実機/App Store 用ビルドの前には削除してください。不要なデバイスで署名の問題が発生します。

⚠️ iOS:アンインストールしてもキーチェーンは消去されない

Android とは異なり、iOS アプリをアンインストールしても、キーチェーンのエントリーは削除されません。値は保持され、アプリを再インストールすると利用できます。この動作を前提に設計してください。新規インストール時にストレージが空だと想定しないでください。

⚠️ iOS:iCloud キーチェーンの同期

ユーザーが iCloud キーチェーンを有効にしている場合、値は iCloud キーチェーンを通じてデバイス間で同期されることがあります。これはプラットフォームの動作であり、MAUI から制御できません。同期されるべきでないデバイス固有のトークンは保存しないでください。

Windows の制限
  • キー名:最大 255 文字
  • 値:設定ごとに最大 8 KB
  • 複合ストレージ:合計最大 64 KB

よくある間違い

// ❌ Storing large data — SecureStorage is for small secrets only
await SecureStorage.Default.SetAsync("profile_image", base64EncodedImage);

// ✅ Store tokens, passwords, short secrets
await SecureStorage.Default.SetAsync("auth_token", jwtToken);

// ❌ Logging secret values
_logger.LogInformation("Token: {Token}", await SecureStorage.Default.GetAsync("auth_token"));

// ✅ Log existence, not value
_logger.LogInformation("Token exists: {Exists}", token is not null);

// ❌ Storing complex objects without serialization (values are strings only)
await SecureStorage.Default.SetAsync("user", userObject);

// ✅ Serialize to JSON first
await SecureStorage.Default.SetAsync("user", JsonSerializer.Serialize(user));

判断の指針

質問回答
トークン、パスワード、API キーを保存しますか?✅ SecureStorage を使用します
ユーザーの設定や環境設定を保存しますか?❌ 代わりに Preferences を使用します
大きなファイルやバイナリデータを保存しますか?❌ ファイルシステムと暗号化を使用します
デバイス間の同期が必要ですか?⚠️ iOS では iCloud キーチェーンを通じて自動的に同期されます
アンインストール時にデータを消去する必要がありますか?⚠️ Android でのみ可能で、iOS ではできません

テスト容易性:必ず DI を使用する

テスト容易性を確保するため、ViewModel から SecureStorage.Default を直接呼び出さないでください。代わりに ISecureStorageService インターフェイスでラップしてください。モックの例を含む DI ラッパーの完全なパターンについては、references/secure-storage-api.md を参照してください。

// ❌ Direct static access — untestable
public class LoginViewModel
{
    public async Task SaveToken(string token)
        => await SecureStorage.Default.SetAsync("auth_token", token);
}

// ✅ Inject interface — testable and mockable
public class LoginViewModel(ISecureStorageService secure)
{
    public async Task SaveToken(string token)
        => await secure.SetAsync("auth_token", token);
}

チェックリスト

  • Android:自動バックアップを無効にするか、セキュアストレージをバックアップ対象から除外する
  • Android:すべての GetAsync 呼び出しを try/catch で囲む
  • iOS:キーチェーンエンタイトルメントを設定する(シミュレーターのみ。実機用ビルドでは削除する)
  • 値は文字列のみとし、複雑なデータは JSON にシリアライズする
  • シークレット値をどこにもログ出力しない
  • SecureStorage.Default は ViewModel から直接ではなく、DI ラッパー経由でアクセスする

コメント