Flutter secure_storage: Store Tokens and Secrets Safely

Coding Liquids blog cover featuring Sagnik Bhattacharya for Flutter secure_storage: Store Tokens and Secrets Safely, with flutter_secure_storage read, write, and delete, keychain and android keystore protection, and platform options and encrypted preference migration.
Coding Liquids blog cover featuring Sagnik Bhattacharya for Flutter secure_storage: Store Tokens and Secrets Safely, with flutter_secure_storage read, write, and delete, keychain and android keystore protection, and platform options and encrypted preference migration.

A Coding Liquids session can show login, restoration, and logout against the same protected key.

Subscribe on YouTube@codingliquids

Use the platform credential stores

flutter_secure_storage delegates to Keychain on Apple platforms and Keystore-backed protection on Android. It is appropriate for tokens, small secrets, and cryptographic key material rather than application records.

Protection at rest does not stop a compromised runtime from reading a token the app itself can access.

Write read and erase secret values

write stores a string under a key, read returns a nullable string, delete removes one key, and deleteAll clears the store. Await every operation and centralise key names so login and logout agree on the exact identifier.

Treating a missing token as an exception complicates the normal signed-out state.

flutter pub add flutter_secure_storage

The Instagram token-lifecycle map separates protected storage, refresh, revocation, and biometric policy.

Follow me on Instagram@sagnikteaches

Give authentication a TokenStorage API

A small TokenStorage exposes saveRefreshToken, readRefreshToken, and clearSession instead of raw storage calls. That vocabulary keeps widgets unaware of platform options and makes the auth repository testable with a fake.

A general-purpose map of secret strings invites unrelated features to share sensitive keys.

The Complete Flutter Guide course thumbnail

Build production-ready secure_storage features

The Complete Flutter Guide turns secure_storage into maintainable app architecture, polished UI, and testable production code.

Enrol now

The storage plugin is the only runtime package required by the TokenStorage wrapper.

dependencies:
  flutter:
    sdk: flutter
  flutter_secure_storage: any

Configure Android and iOS behaviour

AndroidOptions can request encryptedSharedPreferences for the supported plugin path, while IOSOptions controls Keychain accessibility. Choose an iOS accessibility level that matches whether background access is required and verify device-lock behaviour.

Copying platform options from an old plugin version can select deprecated storage or migration behaviour.

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

Connect token lifetime to auth events

Save the refresh token only after a successful login, read it during session restoration, and erase it on logout or revocation. Keep short-lived access tokens in memory when practical and let the auth layer refresh them.

Writing the token before the server confirms authentication can restore a session that never became valid.

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class TokenStore {
  const TokenStore(this.storage);
  final FlutterSecureStorage storage;
  static const key = 'refresh_token';

  Future<void> write(String token) => storage.write(key: key, value: token);
  Future<String?> read() => storage.read(key: key);
  Future<void> delete() => storage.delete(key: key);
}

const tokenStore = TokenStore(FlutterSecureStorage());

Secure client boundaries get a fuller treatment in my LinkedIn discussion of loss, rotation, and compromised devices.

Connect on LinkedInSagnik Bhattacharya
The Complete Flutter Guide course thumbnail

The Complete Flutter Guide: Build Android, iOS and Web apps

Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.

Enrol now

Keep payloads small and separate biometrics

Secure storage is designed for small values, not profile photos, cached API responses, or entire databases. Biometric prompts require a separate authentication design; encrypted storage alone does not automatically ask for Face ID or fingerprint.

Large blobs make operations slow and blur the distinction between secret management and persistence.

const storage = FlutterSecureStorage(
  aOptions: AndroidOptions(encryptedSharedPreferences: true),
  iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);

final tokens = TokenStorage(storage);
await tokens.saveRefreshToken(response.refreshToken);
final restored = await tokens.readRefreshToken();
await tokens.clearSession();

Handle iOS reinstall persistence

Keychain items may survive app deletion and reinstallation depending on platform behaviour and entitlements. Record a non-secret first-launch marker elsewhere and clear obsolete keys when a truly fresh installation is detected.

Assuming uninstall always signs the user out can resurrect an unexpected account on a shared device.

Design for loss, rotation, and lock state

Secure storage is a protected key-value facility, not a complete session manager. Keep access and refresh tokens behind an authentication repository so widgets never decide how credentials are named, refreshed, or erased. Save the minimum needed to resume a session, and treat a missing value as a normal signed-out state. Device migration, backup policy, biometric changes, or an operating-system reset can make protected material unavailable even though the app’s ordinary files remain.

Key rotation needs two identifiers: the storage key used by the app and the cryptographic or server-side version represented by its value. Read the current key first, fall back to the previous one during a controlled migration, then remove the old entry only after the replacement has been confirmed. If a token write fails, do not leave the interface claiming that sign-in will survive restart. Surface a recoverable error or keep the session memory-only and explain what will happen.

Platform exceptions should be classified rather than swallowed. A temporarily locked keychain can be retried after device unlock, while a permanently invalidated Android key may require clearing the unusable entry and authenticating again. Test both paths by injecting a storage abstraction that throws representative failures. Simultaneous refresh requests should share one in-flight operation so an older token cannot overwrite a newer one after completing late.

Sign-out must clear every credential that grants access, cancel authorised network work, and remove account-scoped caches. Perform server revocation when supported, but still erase the local material if the device is offline. Never print secret values in crash reports, analytics, or debug output; log the operation name and failure category only. On a rooted or compromised device no client store offers absolute protection, so short token lifetimes, server validation, revocation, and least-privilege scopes remain essential parts of the boundary.

Biometric gating and secure storage solve different problems. A keychain or keystore item may remain available after the device is unlocked even when the feature wants fresh user presence. Put that policy in a separate authentication step and choose the platform accessibility option that matches it, then test device reboot, biometric enrolment changes, passcode removal, and app restore. Keys used to encrypt a local database should be generated with suitable entropy and copied only through protected memory paths. If that key disappears, present a clear recovery or reset route; repeatedly retrying decryption cannot reconstruct it.

Web targets require a separate threat review because browser storage and cryptographic guarantees differ from mobile keychains and keystores. Do not promise identical protection merely because one Dart API spans the platforms. Consider whether the web session should use secure, server-managed cookies instead of a token readable by client code. Test sign-out in multiple tabs and ensure a revoked session stops authorised requests everywhere. Documentation should name the supported platforms and recovery behaviour so callers know which guarantees they can actually rely on.

Common mistakes

  • Use the platform credential stores: In secure token storage, protection at rest does not stop a compromised runtime from reading a token the app itself can access; inspect this secure token storage cause before changing another secure token storage widget.
  • Write read and erase secret values: In secure token storage, treating a missing token as an exception complicates the normal signed-out state; inspect this secure token storage cause before changing another secure token storage widget.
  • Give authentication a TokenStorage API: In secure token storage, a general-purpose map of secret strings invites unrelated features to share sensitive keys; inspect this secure token storage cause before changing another secure token storage widget.
  • Configure Android and iOS behaviour: In secure token storage, copying platform options from an old plugin version can select deprecated storage or migration behaviour; inspect this secure token storage cause before changing another secure token storage widget.
  • Connect token lifetime to auth events: In secure token storage, writing the token before the server confirms authentication can restore a session that never became valid; inspect this secure token storage cause before changing another secure token storage widget.
  • Keep payloads small and separate biometrics: In secure token storage, large blobs make operations slow and blur the distinction between secret management and persistence; inspect this secure token storage cause before changing another secure token storage widget.

Frequently asked questions

How does use the platform credential stores work in secure token storage?

For secure token storage, the starting rule is that flutter_secure_storage delegates to Keychain on Apple platforms and Keystore-backed protection on Android. Apply this secure token storage rule first because use the platform credential stores determines whether the secure token storage pattern fits.

Why does give authentication a TokenStorage API matter for secure token storage?

In secure token storage, that vocabulary keeps widgets unaware of platform options and makes the auth repository testable with a fake. Keeping give authentication a TokenStorage API at the secure token storage call site exposes the secure token storage return value directly.

What failure should I test first in secure token storage?

First reproduce the secure token storage case where writing the token before the server confirms authentication can restore a session that never became valid. The follow-up test stores credentials only after authentication succeeds and removes them when the corresponding session ends.

How can I verify secure token storage before release?

Exercise handle iOS reinstall persistence with real secure token storage inputs on every shipped platform. Inspect the final secure token storage callback or output; a successful secure token storage button tap alone is not proof.

Further reads

Connect secure token storage to the surrounding Flutter stack through these published tutorials:

Sources: Flutter documentation and the package documentation on pub.dev; secure token storage examples verified against current stable Flutter.