flutter_secure_storage in Flutter is the go-to way to store small secrets like access tokens, refresh tokens, and encryption keys. It uses the platform’s secure storage (Android Keystore, iOS Keychain, etc.) so you don’t have to roll your own crypto. It fits into a broader storage strategy together with Hive and shared_preferences, which we compare in Choosing Hive, shared_preferences, or flutter_secure_storage in Flutter.
Audience: IntermediateTested on: Flutter 3.x, Dart 3.x, Android 14 / iOS 17, macOS 14
What flutter_secure_storage is for
Use flutter_secure_storage for small, sensitive values that must not be stored in plain text:
- Access and refresh tokens
- Symmetric encryption keys
- Local PINs or “biometric enabled” flags
- Per-device identifiers that should be protected
It is not a general database. For normal data and caches, pair it with Hive and shared_preferences as described in the comparison article.
Installing flutter_secure_storage
Add the package to your project:
# pubspec.yaml
dependencies:
flutter_secure_storage: ^latest
Then run:
flutter pub get
Basic usage: saving and reading a token
The API is a simple async key–value interface. Wrap it in a small service so the rest of your app doesn’t depend on raw keys.
// lib/services/secure_store.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStore {
SecureStore(this._storage);
final FlutterSecureStorage _storage;
static const _keyAccessToken = 'accessToken';
static const _keyRefreshToken = 'refreshToken';
Future<void> saveTokens({
required String accessToken,
required String refreshToken,
}) async {
await _storage.write(key: _keyAccessToken, value: accessToken);
await _storage.write(key: _keyRefreshToken, value: refreshToken);
}
Future<String?> readAccessToken() =>
_storage.read(key: _keyAccessToken);
Future<String?> readRefreshToken() =>
_storage.read(key: _keyRefreshToken);
Future<void> clearTokens() async {
await _storage.delete(key: _keyAccessToken);
await _storage.delete(key: _keyRefreshToken);
}
}
Inject SecureStore into your auth repository or use-case layer. That way, any future migration (e.g., rotating keys) is localized.
Platform behavior and options
Under the hood:
- Android: Uses EncryptedSharedPreferences / Keystore (AES + MasterKey) depending on API level.
- iOS: Uses Keychain with configurable accessibility options.
- Desktop/Web: Behavior depends on plugin support; check the package docs if you target these platforms.
You can customize options when creating the storage (for example, to control iOS accessibility or Android encryption settings).
Integrating with authentication flows
A typical pattern is:
- Sign-in: Call your backend; on success, store tokens in
SecureStore. - App startup: Read tokens from
SecureStore. If present, treat the user as signed in and refresh if needed. - API calls: Attach access token from memory; when it expires, use refresh token from
SecureStoreto obtain a new pair. - Logout: Clear tokens from secure storage and wipe any cached user data in Hive/shared_preferences.
// lib/repositories/auth_repository.dart
class AuthRepository {
AuthRepository({required this.secureStore});
final SecureStore secureStore;
Future<void> signIn(String email, String password) async {
// 1) Call API (pseudo-code)
final tokens = await _api.signIn(email, password);
// 2) Persist securely
await secureStore.saveTokens(
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
);
}
Future<void> signOut() async {
await secureStore.clearTokens();
// Also clear caches / prefs as needed.
}
}
Storing encryption keys for other storage layers
When you need to encrypt a Hive box or another local store, the encryption key itself should live in secure storage, not in code or plain files.
// Generate & persist a new key once
Future<void> createHiveKey(SecureStore store) async {
final key = Hive.generateSecureKey(); // 32 random bytes
await store.saveHiveKey(key); // write as base64/string inside SecureStore
}
// Later, load key for encrypted box
Future<List<int>> loadHiveKey(SecureStore store) async {
final key = await store.readHiveKeyBytes();
return key;
}
This separation (key in secure storage, bulk data in Hive) is exactly the layered approach described in Choosing Hive, shared_preferences, or flutter_secure_storage in Flutter.
Handling logout and “wipe device” scenarios
On logout, you usually want to remove both tokens and cached data:
- Delete tokens from
flutter_secure_storage. - Clear user-specific Hive boxes (profiles, timelines, drafts).
- Reset user-bound preferences in shared_preferences (email, onboarding flags).
Wrap this in a single “signOutAndWipeLocal” use case so you don’t forget any piece.
Security & pitfalls
- Not for large blobs. Secure storage is for small secrets, not megabytes of JSON or images. Store only what you must protect.
- Backups and rooted devices. Even with secure storage, treat tokens as revocable. On rooted/jailbroken devices, attackers may still extract secrets.
- Hard-coded secrets. Never ship API keys or encryption keys hard-coded in your Dart code. If you must have static keys, keep them server-side or inject them via build-time tooling plus secure storage.
- Ignoring errors. Always handle read/write failures gracefully (e.g., show a “please sign in again” flow if secure storage becomes unavailable).
FAQ
Q: Can I use flutter_secure_storage on the web?
A: Web support is limited and depends on the implementation; check the current package documentation. For browser apps, assume local storage can be cleared easily and treat refresh tokens accordingly.
Q: Should I store user IDs or emails in secure storage?
A: It’s not mandatory, but if you’re concerned about privacy or abuse on shared devices, keeping identifiers in secure storage is reasonable. For truly sensitive data, always err on the side of more protection.
Q: Do I still need backend security if I use secure storage?
A: Absolutely. Secure storage protects tokens on the device, but backend APIs still need proper authentication, authorization, rate limiting, and token revocation.
Conclusion
flutter_secure_storage in Flutter gives your app a safe place to keep the small secrets that power authentication and encryption. Use it alongside Hive for bulk data and shared_preferences for simple flags—as outlined in the broader storage comparison—to build a layered, security-conscious architecture that’s still ergonomic to work with.
Updated: 2025-11-12


Comment