Himanshu Sharma Computer science student pursuing his Bachelor's and working as an SWE Intern. For the past 2 years, has been developing mobile and web apps using Flutter SDK. Open-source enthusiast and co-organizer of Flutter India.

Securing local storage in Flutter

7 min read 1980

Securing Local Storage In Flutter

Local storage is a crucial part of mobile app development for maintaining and preserving users’ data until the app is removed. Today, an app’s trustworthiness hinges upon how its data is being saved.

In this tutorial, you’ll learn how to securely save data locally from your Flutter app.

You might be wondering why we need local storage when we can save the data on a server. What if you need the data in your app in offline mode? You don’t want to show the “No Connectivity” warning to your end users, which is why local storage is important.

So, how do you save the data locally? There are several ways to do that in Flutter, but you’ll use the flutter_secure_storage package for this tutorial.

N.B., if you are new to Flutter, please go through the official documentation to learn about it.

Why use flutter-secure-storage?

flutter_secure_storage is a Flutter plugin used to store data in secure storage. So what is this secure storage, and how secure is it? Well, the description of secure storage changes with consideration for the platform.

If the platform is Android, then flutter_secure_storage stores data in encryptedSharedPreference, which are shared preferences that encrypt keys and values. It handles AES encryption to generate a secret key encrypted with RSA and stored in KeyStore.

For the iOS platform, flutter_secure_storage uses the KeyChain which is an iOS-specific secure storage used to store and access cryptographic keys only in your app.

In the case of the web, flutter_secure_storage uses the Web Cryptography (Web Crypto) API.

Getting started with the local storage demo

Download the starter project containing the prebuilt UI and minimal configuration from here.

We made a custom demo for .
No really. Click here to check it out.

Open it in your editor, then build and run the app:

Running The App

The file structure of the starter project looks like this:

File Structure Of The Starter Project

  • main.dart — the entry point for the whole app
  • home_view.dart — contains the Home view where the secured data list will be displayed
  • storage_service.dart — contains the methods responsible for reading, writing, and deleting data from secured storage
  • storage_item.dart — the model or state to store data in a key-value pair:
class StorageItem {
 StorageItem(this.key, this.value);

 final String key;
 final String value;
}
  • widgets — directory containing additional custom widgets to help build the UI:
    • add_data_dialog.dart — a dialog widget used to add new data to the secure storage
    • add_data_dialog.dart — a dialog widget that edits a StorageItem object
    • search_key_value_dialog.dart — a dialog widget to search for a key value
    • textfield_decoration.dart — a widget to add shadow over the input text field in the Dialog widgets
    • vault_card.dart — a custom widget to display a list tile in the home screen and provide features like editing the value, and long-press value visibility

Setting up secure storage

Add the flutter_secure_storage in the pubspec dependencies:

#yaml
flutter_secure_storage: ^5.0.2

Android Configuration

In your project-level build.gradle file, update the minSdkVersion:

//gradle
android {
    ...
    defaultConfig {
        ...
        minSdkVersion 18
        ...
    }
}

Apps targeting API level 23+ automatically back up almost all the data to Google Drive. You can disable it in your app manifest file:

<!--xml-->
<manifest ... >
    ...
    <application android:allowBackup="true" ... >
        ...
    </application>
</manifest>

If you want to control what gets backed up, you need to define a custom XML rule, as mentioned here.

Web Configuration

Be sure to enable HTTP Strict-Transport-Security (HSTS) and other important HTTP security headers because Web Crypto only works when the website is running with HTTPS or localhost.

Linux Configuration

Use Flutter’s manual installation instead of Flutter Snap. Also, you need libsecret-1-dev and libjsoncpp-dev on your machine to build the project, and libsecret-1-0 and libjsoncpp1 to run the application:

sudo apt reinstall libjsoncpp1 libsecret-1-0 libjsoncpp-dev libsecret-1-dev -y

#OR

sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev libblkid-dev liblzma-dev

Secure storage methods

You are now ready to create a class of all methods required to read, write, and delete data from secure storage.

In your lib directory, create a new directory named services and create a secure_storeage.dart file inside it:

Create A New Directory Named Services

Now, create a StorageService class inside the file as below:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class StorageService {
 final _secureStorage = const FlutterSecureStorage();
}

Here you initialized _secureStorage, an instance of FlutterSecureStorage().

Write

Now create the method responsible for writing data into secure storage:

Future<void> writeSecureData(StorageItem newItem) async {
 await _secureStorage.write(
     key: newItem.key, value: newItem.value, aOptions: _getAndroidOptions());
}

In the above code, _getAndroidOptions() is also a method of the StorageService class used to set the encryptedSharedPreference property true:

AndroidOptions _getAndroidOptions() => const AndroidOptions(
     encryptedSharedPreferences: true,
   );

You can also create more options methods concerning your device.

N.B., When upgrading the flutter_secure_storage to 5.0.0 in Android, you can migrate to EncryptedSharedPreferences using the above method. This will auto-migrate all the preferences. Once migrated, this cannot be undone. If you try to disable the encryptedSharedPreference, you’ll be unable to read the value.

Read

Next, create the readSecureData method to read the secured data concerning the key:

Future<String?> readSecureData(String key) async {
 var readData =
     await _secureStorage.read(key: key, aOptions: _getAndroidOptions());
 return readData;
}

Delete

Now, to delete a key-value pair, create the deleteSecureData method as below:

Future<void> deleteSecureData(StorageItem item) async {
 await _secureStorage.delete(key: item.key, aOptions: _getAndroidOptions());
}

containsKey

Create a containsKeyInSecureData method responsible for checking whether the storage contains the provided key or not:

Future<bool> containsKeyInSecureData(String key) async {
 var containsKey = await _secureStorage.containsKey(key: key, aOptions: _getAndroidOptions());
 return containsKey;
}

readAll

To read all the secured data, create the readAllSecureData method as below:

Future<List<StorageItem>> readAllSecureData() async {
 var allData = await _secureStorage.readAll(aOptions: _getAndroidOptions());
 List<StorageItem> list =
     allData.entries.map((e) => StorageItem(e.key, e.value)).toList();
 return list;
}

In the above code, you return a list of StorageItems after reading all the data.

deleteAll

Next, to delete all the secured data, create the deleteAllSecureData method:

Future<void> deleteAllSecureData() async {
 await _secureStorage.deleteAll(aOptions: _getAndroidOptions());
}

Using these methods in your Flutter app

Now, you’ll update the starter project so that you can utilize the above methods.

Reading all the data

Let’s start with reading all the data present in the storage and displaying it in the UI. So, in the home_view.dart, first initialize the StorageService instance:

final StorageService _storageService = StorageService();

Next, update the initList() method:

void initList() async {
    _items = await _storageService.readAllSecureData();
    _loading = false;
    setState(() {});
  }

In the above code, you are using the readAllSecureData method to update and set the list in the initState.

This will auto-update the home screen if the data exits as we already have a ListView.builder widget rendering each list item using the VaultCard widget.

Writing new data

To write new data, first update the AddDataDialog widget:

ElevatedButton(
                    onPressed: () {
                      final StorageItem storageItem = StorageItem(
                          _keyController.text, _valueController.text);
                      Navigator.of(context).pop(storageItem);
                    },
                    child: const Text('Secure'))

In the above code, you used the _keyController and _valueController controllers to create a new StorageItem object and return it to the home screen.

Next, use the onPressed property of the Add Data button from your home_view.dart:

ElevatedButton(
 onPressed: () async {
   // 1
   final StorageItem? newItem = await showDialog<StorageItem>(
       context: context, builder: (_) => AddDataDialog());
   if (newItem != null) {
     // 2
     _storageService.writeSecureData(newItem).then((value) {
       setState(() {
         _loading = true;
       });
       // 3
       initList();
     });
   }
 },
 child: const Text("Add Data"),
)

In the above code, you did the following:

  1. newItem: added a nullable StorageItem object coming from the AddDataDialog widget
  2. Passed the newItem object into the writeSecureData method to write new data
  3. Used the initList method to fetch all the existing data from secure storage

Build and restart your app, and you’ll be able to write new data in secure storage.

Write New Data In Secure Storage

Deleting data

Now, to delete any particular data from the list, update the ListView.builder widget in the home_view.dart:

ListView.builder(
                    itemCount: _items.length,
                    padding: const EdgeInsets.symmetric(horizontal: 8),
                    itemBuilder: (_, index) {
              // 1
                      return Dismissible(
                        // 2
                        key: Key(_items[index].toString()),
                        child: VaultCard(item: _items[index]),
                        // 3
                        onDismissed: (direction) async {
                          // 4
                          await _storageService
                              .deleteSecureData(_items[index])
                              .then((value) => _items.removeAt(index));
                          // 5
                          initList();
                        },
                      );
                    })

In the above code, you did the following:

  1. Wrapped the VaultCard widget with the Dismissible widget to dismiss the respective card horizontally
  2. Used the Key widget with the help of the current list item to create a unique key for the mandatory key property of the Dismissible widget. You can have random numeric or alphanumeric values as well
  3. Used the onDismissed property of the Dismissible widget to remove the particular card/data from the list
  4. With the help of the deleteSecureData, you passed the respective element and deleted it. However, make sure to remove the item from the list as well, else the Dismissible widget will throw an error
  5. Used the initList() method to update the list after deleting the item

Build and restart your app, and you’ll be able to delete data from the secure storage.

Deleting Data From The Secure Storage

Searching for data

To search existing data, you need the key to use it in the SearchKeyValueDialog widget. So update the AppBar in your home_view.dart to show the SearchKeyValueDialog as below:

 IconButton(
            icon: const Icon(Icons.search, color: Colors.black),
            onPressed: () => showDialog(
                context: context, builder: (_) => const SearchKeyValueDialog()),
          )

Next, initialize the StorageService instance in the SearchKeyValueDialog widget and update the onPressed property of the Search button:

ElevatedButton(
                    onPressed: () async {
                      _value = await _storageService
                          .readSecureData(_keyController.text);
                      setState(() {});
                    },
                    child: const Text('Search'))

Here you have updated the _value by using the readSecureData method and passing the key in it. This will auto-update the dialog and display the value below the button.

Build and restart your app.

Build And Restart The App

Deleting all the data

To delete all the data, go back to the home_view.dart and update the Delete All Data button:

ElevatedButton(
                  style: ElevatedButton.styleFrom(primary: Colors.red),
                  onPressed: () async {
                    _storageService
                        .deleteAllSecureData()
                        .then((value) => initList());
                  },
                  child: const Text("Delete All Data"),
                )

Here, you utilized the deleteAllSecureData method and updated the list using the initList method.

Utilized Deleteallsecuredata Method

Edit storage items

Editing existing data is performed in the VaultCard widget, so update it by initializing an instance of the StorageService:

final StorageService _storageService = StorageService();

Next, use the EditDataDialog widget to get the updated value back in the VaultCard widget using the StorageItem. Update the EditDataDialog:

final StorageItem item;

const EditDataDialog({Key? key, required this.item}) : super(key: key);

You update the constructor in the above code to receive the StorageItem object.

Next, update the Update button:

ElevatedButton(
                    onPressed: () {
                      Navigator.of(context).pop(_valueController.text);
                    },
                    child: const Text('Update'))

Here you return the updated value to the VaultCard widget.

Now, update the onPressed property of the edit IconButton in the VaultCard:

IconButton(
              icon: const Icon(Icons.edit),
              onPressed: () async {
                // 1
                final String updatedValue = await showDialog(
                    context: context,
                    builder: (_) => EditDataDialog(item: widget.item));
                if (updatedValue.isNotEmpty) {
                  // 2
                  _storageService
                      .writeSecureData(
                          StorageItem(widget.item.key, updatedValue))
                      .then((value) {
                     // 3
                    widget.item = StorageItem(widget.item.key, updatedValue);
                    setState(() {});
                  });
                }
              },
            )

In the above code, you did the following:

  1. Received the updated value by passing the StorageItem object as an argument to the EditDataDialog widget
  2. Updated the data using the writeSecureData method if the received value is not empty
  3. After the update, you updated the StorageItem object itself

Build and restart your app.

Build And Restart The App

So this is how you can use the flutter_secure_storage. You can also use it at the time of authentication to store user data.

Conclusion

You can find the final project here.

In this tutorial, you learned about flutter_secure_storage and how you can use it in your Flutter app. For the next step, you can use any state management solution with secure storage (such as GetX) for more efficiency, or learn about using the sqlcipher library on both iOS and Android devices, which also provides the ability to store data securely.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Himanshu Sharma Computer science student pursuing his Bachelor's and working as an SWE Intern. For the past 2 years, has been developing mobile and web apps using Flutter SDK. Open-source enthusiast and co-organizer of Flutter India.

Leave a Reply