【Flutter】Keychainアクセスのオプションと、iOSネイティブ連携してる場合のハマりポイント
flutter_secure_storage
前提として、Flutter側はflutter_secure_storageを使用しています。
FlutterにおけるiOSのKeychainアクセスにはこのパッケージが有名だと思います。
パッケージの実装の概要としては、MethodChannel[1]でプラットフォーム側のセキュア情報にアクセスします。
iOSのKeychainアクセスにおけるオプション
flutter_secure_storageではKeychainAccessibilityというオプションが用意されており、端末のアンロック状態やパスコード設定有無によってアクセス制限の堅さ分けがされています。
| 属性 | 概要 | デバイス制約 |
|---|---|---|
| passcode | 端末にパスコードが設定されていてロック解除中のみアクセス可能。この中では一番堅い。 | あり(※) |
| unlocked | ロック解除中のみアクセス可能。デフォルト。 | なし |
| unlocked_this_device | ロック解除中のみアクセス可能。 | あり(※) |
| first_unlock | 端末の再起動後に一度ロック解除されるまでアクセスできない。(再起動後のロック解除後はロック画面中でもアクセス可能) | なし |
| first_unlock_this_device | 端末の再起動後に一度ロック解除されるまでアクセスできない。(再起動後のロック解除後はロック画面中でもアクセス可能) | あり(※) |
ピュアFlutterの場合はシンプル
ピュアFlutterの場合は、上述のflutter_secure_storageのKeychainAccessibilityを用途によって使い分ければ良いです。
基本的にはデフォルトで足りると思いますが、例えばロック画面でもアクセスしたいならfirst_unlockを使います。
final options = IOSOptions(accessibility: KeychainAccessibility.first_unlock);
await storage.write(key: key, value: value, iOptions: options);
ネイティブ連携してる場合の注意⚠️
ここからのお話は、デフォルト(つまりunlocked)の場合は関係ないかもしれません。
次の場合は注意が必要です
- ネイティブ連携していて、かつネイティブiOS側(Swift)とFlutterの両方から同じKeychainの値にアクセスしている
- さらに
first_unlockなどデフォルト以外を使っている場合
- さらに
私はこのパターンに当てはまっていたので、Flutterでfirst_unlockを設定しているのにロック画面でKeychainからデータ取得できずハマりました😭
原因は、Swift側ではデフォルト(unlocked)になっていたことです。
For example, by default, you can only access keychain items when the device is unlocked.
When Unlocked
Items with this setting are only accessible when the device is unlocked. A device without a passcode is considered to always be unlocked. This is the default accessibility when you don’t otherwise specify a setting.
結論として、FlutterとSwift側で同じ属性を設定できていれば問題はありません。
first_unlockなどデフォルト以外を使うときは、Swift側にも設定が必要です。
iOS(Swift)側の設定
要はflutter_secure_storageと同じ感じで、kSecAttrAccessibleAfterFirstUnlockなど任意の属性を選ぶだけです!

公式ドキュメントより
私はネイティブiOSのKeychainアクセスにはkishikawakatsumiさんのKeychainAccessを使っています。
Flutterでfirst_unlockを指定した場合
final options = IOSOptions(accessibility: KeychainAccessibility.first_unlock);
await storage.write(key: key, value: value, iOptions: options);
Swiftでもこのように書けば、両者でアクセスが可能です!
let keychain = Keychain(service: "com.example.github-token")
.accessibility(.afterFirstUnlock)
参考
- https://blog.kishikawakatsumi.com/entry/2015/01/03/082916
- https://qiita.com/sachiko-kame/items/261d42c57207e4b7002a
- https://flutter.tnantoka.com/entry/2019/05/29/225128
- https://zenn.dev/yokojp/articles/0ff8fc44f48857
Discussion