Flutter Secure Storage
Library
Flutter Secure Storage
Overview
Flutter Secure Storage is a Flutter plugin for securely storing sensitive data using platform-specific secure storage mechanisms.
Details
Flutter Secure Storage is a Flutter plugin that encrypts and stores sensitive data such as authentication tokens, API keys, and user credentials in key-value format. It works across multiple platforms including Android, iOS, macOS, Windows, and Linux, utilizing the optimal security features of each platform while providing a unified API.
The plugin adopts a federated plugin architecture, separating the platform-agnostic API from platform-specific implementations. This allows the use of optimal native security features for each platform while providing developers with a consistent API.
Platform-specific implementations are as follows:
- Android: Uses EncryptedSharedPreferences with Google Tink for encryption
- iOS/macOS: Utilizes Apple Keychain Services API
- Windows: Encrypts using Data Protection API (DPAPI) and stores in JSON files
- Linux: Uses Secret Service API (libsecret) to access system keyring
- Web: Uses WebCrypto API for encryption and localStorage/sessionStorage (works only on HTTPS or localhost)
The data flow involves the Flutter application calling methods on the FlutterSecureStorage instance, which delegates to the FlutterSecureStoragePlatform instance. This platform interface, typically MethodChannelFlutterSecureStorage, invokes platform-specific code through method channels. The native layer handles encryption and storage using secure mechanisms and returns results to the Flutter app.
Pros and Cons
Pros
- Multi-platform Support: Support for multiple platforms with a single API
- Platform Optimization: Utilizes optimal security features of each OS
- Encryption: Data is automatically encrypted before storage
- Easy Implementation: Intuitive key-value API that's easy to use
- Listener System: Ability to monitor key changes
- Configuration Options: Platform-specific configurations available
- Active Maintenance: Ongoing development and community support
Cons
- Platform Dependency: Depends on each platform's security features
- Debugging Difficulty: Debugging encrypted data can be challenging
- iOS Keychain Limitations: May be affected by iOS Keychain capacity limits
- Web Restrictions: Works only in HTTPS environment or localhost
- Not for Large Data: Not suitable for storing large amounts of data
- Platform-specific Errors: Need to handle errors from each OS's security features
Key Links
- Flutter Secure Storage pub.dev
- GitHub Repository
- API Documentation
- Flutter Official Plugins
- iOS Keychain Services
- Android EncryptedSharedPreferences
Usage Examples
Basic Setup
// Add to pubspec.yaml
dependencies:
flutter_secure_storage: ^9.2.2
// Initialize before use
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
// Create instance with default settings
const storage = FlutterSecureStorage();
// Create instance with custom settings
const storage = FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
iOptions: IOSOptions(
groupId: 'com.example.app',
accountName: 'MyApp',
accessibility: KeychainAccessibility.first_unlock,
),
lOptions: LinuxOptions(
useSessionKeyring: false,
),
webOptions: WebOptions(
dbName: 'MyAppStorage',
publicKey: 'MyPublicKey',
),
);
Basic CRUD Operations
class SecureStorageService {
static const _storage = FlutterSecureStorage();
// Write data
static Future<void> writeData(String key, String value) async {
try {
await _storage.write(key: key, value: value);
print('Data saved: $key');
} catch (e) {
print('Write error: $e');
throw Exception('Failed to save data');
}
}
// Read data
static Future<String?> readData(String key) async {
try {
final value = await _storage.read(key: key);
return value;
} catch (e) {
print('Read error: $e');
return null;
}
}
// Delete data
static Future<void> deleteData(String key) async {
try {
await _storage.delete(key: key);
print('Data deleted: $key');
} catch (e) {
print('Delete error: $e');
throw Exception('Failed to delete data');
}
}
// Delete all data
static Future<void> deleteAllData() async {
try {
await _storage.deleteAll();
print('All data deleted');
} catch (e) {
print('Delete all error: $e');
throw Exception('Failed to delete all data');
}
}
// Check if key exists
static Future<bool> containsKey(String key) async {
try {
return await _storage.containsKey(key: key);
} catch (e) {
print('Key check error: $e');
return false;
}
}
// Get all key-value pairs
static Future<Map<String, String>> readAllData() async {
try {
return await _storage.readAll();
} catch (e) {
print('Read all error: $e');
return {};
}
}
}
User Authentication Information Management
class AuthStorageService {
static const _storage = FlutterSecureStorage();
// Key constants
static const _accessTokenKey = 'access_token';
static const _refreshTokenKey = 'refresh_token';
static const _userIdKey = 'user_id';
static const _userEmailKey = 'user_email';
// Save access token
static Future<void> saveAccessToken(String token) async {
await _storage.write(key: _accessTokenKey, value: token);
}
// Get access token
static Future<String?> getAccessToken() async {
return await _storage.read(key: _accessTokenKey);
}
// Save refresh token
static Future<void> saveRefreshToken(String token) async {
await _storage.write(key: _refreshTokenKey, value: token);
}
// Get refresh token
static Future<String?> getRefreshToken() async {
return await _storage.read(key: _refreshTokenKey);
}
// Save user information
static Future<void> saveUserInfo(String userId, String email) async {
await Future.wait([
_storage.write(key: _userIdKey, value: userId),
_storage.write(key: _userEmailKey, value: email),
]);
}
// Get user information
static Future<Map<String, String?>> getUserInfo() async {
final results = await Future.wait([
_storage.read(key: _userIdKey),
_storage.read(key: _userEmailKey),
]);
return {
'userId': results[0],
'email': results[1],
};
}
// Logout (clear all auth data)
static Future<void> clearAuthData() async {
await Future.wait([
_storage.delete(key: _accessTokenKey),
_storage.delete(key: _refreshTokenKey),
_storage.delete(key: _userIdKey),
_storage.delete(key: _userEmailKey),
]);
}
// Check login status
static Future<bool> isLoggedIn() async {
final accessToken = await getAccessToken();
return accessToken != null && accessToken.isNotEmpty;
}
}
JSON Format Data Storage and Retrieval
import 'dart:convert';
class JsonStorageService {
static const _storage = FlutterSecureStorage();
// Save JSON object
static Future<void> saveJsonData<T>(String key, T data) async {
try {
final jsonString = jsonEncode(data);
await _storage.write(key: key, value: jsonString);
} catch (e) {
throw Exception('Failed to save JSON data: $e');
}
}
// Load JSON object
static Future<T?> loadJsonData<T>(
String key,
T Function(Map<String, dynamic>) fromJson
) async {
try {
final jsonString = await _storage.read(key: key);
if (jsonString == null) return null;
final jsonMap = jsonDecode(jsonString) as Map<String, dynamic>;
return fromJson(jsonMap);
} catch (e) {
print('JSON data load error: $e');
return null;
}
}
// Save list data
static Future<void> saveJsonList<T>(String key, List<T> dataList) async {
try {
final jsonString = jsonEncode(dataList);
await _storage.write(key: key, value: jsonString);
} catch (e) {
throw Exception('Failed to save JSON list: $e');
}
}
// Load list data
static Future<List<T>> loadJsonList<T>(
String key,
T Function(Map<String, dynamic>) fromJson
) async {
try {
final jsonString = await _storage.read(key: key);
if (jsonString == null) return [];
final jsonList = jsonDecode(jsonString) as List;
return jsonList
.cast<Map<String, dynamic>>()
.map((json) => fromJson(json))
.toList();
} catch (e) {
print('JSON list load error: $e');
return [];
}
}
}
// Usage example: Save user settings
class UserSettings {
final String theme;
final bool notifications;
final String language;
UserSettings({
required this.theme,
required this.notifications,
required this.language,
});
Map<String, dynamic> toJson() => {
'theme': theme,
'notifications': notifications,
'language': language,
};
factory UserSettings.fromJson(Map<String, dynamic> json) => UserSettings(
theme: json['theme'],
notifications: json['notifications'],
language: json['language'],
);
}
// Settings save/load
class SettingsService {
static const _settingsKey = 'user_settings';
static Future<void> saveSettings(UserSettings settings) async {
await JsonStorageService.saveJsonData(_settingsKey, settings.toJson());
}
static Future<UserSettings?> loadSettings() async {
return await JsonStorageService.loadJsonData(
_settingsKey,
UserSettings.fromJson,
);
}
}
Platform-specific Configurations
class PlatformSpecificStorage {
// iOS-specific settings
static const iosStorage = FlutterSecureStorage(
iOptions: IOSOptions(
// Keychain accessibility settings
accessibility: KeychainAccessibility.first_unlock_this_device,
// App Group sharing
groupId: 'group.com.example.myapp',
// Account name setting
accountName: 'MyAppAccount',
// Sync settings
synchronizable: false,
),
);
// Android-specific settings
static const androidStorage = FlutterSecureStorage(
aOptions: AndroidOptions(
// Use EncryptedSharedPreferences
encryptedSharedPreferences: true,
// When biometric authentication is required
sharedPreferencesName: 'secure_prefs',
preferencesKeyPrefix: 'myapp_',
),
);
// Windows-specific settings
static const windowsStorage = FlutterSecureStorage(
wOptions: WindowsOptions(
// Custom prefix
useBackwardCompatibility: true,
),
);
// Linux-specific settings
static const linuxStorage = FlutterSecureStorage(
lOptions: LinuxOptions(
// Use session keyring
useSessionKeyring: true,
),
);
// Web-specific settings
static const webStorage = FlutterSecureStorage(
webOptions: WebOptions(
// Database name setting
dbName: 'MyAppSecureStorage',
// Public key setting
publicKey: 'myapp_public_key',
),
);
}
Listener System Usage
class StorageListenerService {
static const _storage = FlutterSecureStorage();
static final Map<String, List<ValueChanged<String?>>> _listeners = {};
// Register listener
static void registerListener(String key, ValueChanged<String?> listener) {
if (!_listeners.containsKey(key)) {
_listeners[key] = [];
}
_listeners[key]!.add(listener);
// Use Flutter Secure Storage listener feature
// Note: May not be implemented in current version
}
// Unregister listener
static void unregisterListener(String key, ValueChanged<String?> listener) {
_listeners[key]?.remove(listener);
if (_listeners[key]?.isEmpty == true) {
_listeners.remove(key);
}
}
// Unregister all listeners for specific key
static void unregisterAllListenersForKey(String key) {
_listeners.remove(key);
}
// Unregister all listeners
static void unregisterAllListeners() {
_listeners.clear();
}
// Manual listener call on data change
static Future<void> writeWithNotification(String key, String value) async {
await _storage.write(key: key, value: value);
_notifyListeners(key, value);
}
static Future<void> deleteWithNotification(String key) async {
await _storage.delete(key: key);
_notifyListeners(key, null);
}
// Notify listeners
static void _notifyListeners(String key, String? value) {
_listeners[key]?.forEach((listener) {
listener(value);
});
}
}
// Usage example
class AuthStateManager {
void setupAuthListeners() {
// Monitor access token changes
StorageListenerService.registerListener('access_token', (token) {
if (token != null) {
print('User logged in');
onUserLoggedIn();
} else {
print('User logged out');
onUserLoggedOut();
}
});
}
void onUserLoggedIn() {
// Post-login processing
}
void onUserLoggedOut() {
// Post-logout processing
}
}
Error Handling and Best Practices
class SecureStorageManager {
static const _storage = FlutterSecureStorage();
// Safe data reading
static Future<String?> safeRead(String key) async {
try {
return await _storage.read(key: key);
} on PlatformException catch (e) {
print('Platform error: ${e.message}');
return null;
} catch (e) {
print('Unexpected error: $e');
return null;
}
}
// Safe data writing
static Future<bool> safeWrite(String key, String value) async {
try {
await _storage.write(key: key, value: value);
return true;
} on PlatformException catch (e) {
print('Platform error: ${e.message}');
return false;
} catch (e) {
print('Unexpected error: $e');
return false;
}
}
// Batch operations
static Future<Map<String, String?>> readMultiple(List<String> keys) async {
final results = <String, String?>{};
await Future.wait(
keys.map((key) async {
results[key] = await safeRead(key);
}),
);
return results;
}
// Batch key deletion
static Future<void> deleteMultiple(List<String> keys) async {
await Future.wait(
keys.map((key) => _storage.delete(key: key)),
);
}
// Data backup and restore
static Future<Map<String, String>> backup() async {
try {
return await _storage.readAll();
} catch (e) {
print('Backup error: $e');
return {};
}
}
static Future<bool> restore(Map<String, String> data) async {
try {
// Delete existing data
await _storage.deleteAll();
// Write new data
await Future.wait(
data.entries.map((entry) =>
_storage.write(key: entry.key, value: entry.value)
),
);
return true;
} catch (e) {
print('Restore error: $e');
return false;
}
}
}