Salvare token in modo sicuro in Flutter con flutter_secure_storage
Best practiceIntermedio25 min Flutter 3.x

Salvare token in modo sicuro in Flutter con flutter_secure_storage

Perché non usare shared_preferences per i dati sensibili?

shared_preferences salva i dati in chiaro: chiunque abbia accesso al file system (specie su dispositivi con root/jailbreak) può leggere token, password o chiavi API.

Il pacchetto flutter_secure_storage risolve il problema appoggiandosi ai meccanismi di sicurezza nativi:

  • Keychain su iOS/macOS
  • Keystore + EncryptedSharedPreferences su Android

In questo tutorial creeremo un piccolo servizio riutilizzabile per salvare, leggere ed eliminare un token JWT in modo cifrato, con qualche accorgimento di best practice.

  1. 1

    Aggiungere la dipendenza

    Aggiungi il pacchetto al tuo progetto. Puoi usare il comando flutter pub add oppure modificare manualmente il file pubspec.yaml.

    Dopo l'aggiunta esegui flutter pub get.

    flutter pub add flutter_secure_storage

    Risultato atteso

    Nel pubspec.yaml compare la dipendenza flutter_secure_storage con la relativa versione.

  2. 2

    Configurare le piattaforme native

    Su Android è consigliato usare EncryptedSharedPreferences e impostare minSdkVersion ad almeno 21 in android/app/build.gradle.

    Su iOS non serve configurazione aggiuntiva, ma se vuoi evitare problemi durante il backup del dispositivo puoi impostare l'accessibilità delle voci Keychain (lo vedremo nel prossimo passo).

    // android/app/build.gradle
    android {
        defaultConfig {
            minSdkVersion 21
            // ...
        }
    }

    Risultato atteso

    Il progetto Android compila senza errori legati alla minSdkVersion.

  3. 3

    Creare un servizio riutilizzabile

    Invece di istanziare FlutterSecureStorage sparso nel codice, incapsuliamo la logica in una classe SecureStorageService. Definiamo anche opzioni specifiche per Android e iOS per aumentare la sicurezza.

    L'opzione IOSAccessibility.first_unlock fa sì che il dato sia leggibile solo dopo il primo sblocco del dispositivo dall'avvio, un buon compromesso tra sicurezza e usabilità.

    import 'package:flutter_secure_storage/flutter_secure_storage.dart';
    
    class SecureStorageService {
      SecureStorageService._();
      static final instance = SecureStorageService._();
    
      static const _tokenKey = 'auth_token';
    
      final _storage = const FlutterSecureStorage(
        aOptions: AndroidOptions(
          encryptedSharedPreferences: true,
        ),
        iOptions: IOSOptions(
          accessibility: KeychainAccessibility.first_unlock,
        ),
      );
    
      Future<void> saveToken(String token) async {
        await _storage.write(key: _tokenKey, value: token);
      }
    
      Future<String?> readToken() async {
        return _storage.read(key: _tokenKey);
      }
    
      Future<void> deleteToken() async {
        await _storage.delete(key: _tokenKey);
      }
    
      Future<void> clearAll() async {
        await _storage.deleteAll();
      }
    }

    Risultato atteso

    Hai un singleton SecureStorageService con metodi per salvare, leggere ed eliminare il token.

  4. 4

    Salvare e leggere il token dopo il login

    Simuliamo un flusso di login: dopo aver ottenuto il token dal backend lo salviamo con saveToken, e all'avvio dell'app lo rileggiamo per capire se l'utente è già autenticato.

    Future<void> onLoginSuccess(String jwt) async {
      await SecureStorageService.instance.saveToken(jwt);
    }
    
    Future<bool> isLoggedIn() async {
      final token = await SecureStorageService.instance.readToken();
      return token != null && token.isNotEmpty;
    }

    Risultato atteso

    Il token viene scritto cifrato dopo il login e riletto correttamente al riavvio dell'app.

  5. 5

    Usare il token nelle chiamate e gestire il logout

    Il token salvato serve tipicamente come header Authorization. Vediamo come recuperarlo prima di una richiesta e come cancellarlo al logout.

    Best practice: durante il logout usa deleteToken o clearAll per rimuovere ogni traccia dei dati sensibili.

    import 'package:http/http.dart' as http;
    
    Future<http.Response> fetchProfile() async {
      final token = await SecureStorageService.instance.readToken();
      return http.get(
        Uri.parse('https://api.example.com/me'),
        headers: {
          if (token != null) 'Authorization': 'Bearer $token',
        },
      );
    }
    
    Future<void> logout() async {
      await SecureStorageService.instance.deleteToken();
    }

    Risultato atteso

    Le richieste includono l'header Authorization e al logout il token viene rimosso dallo storage sicuro.

  6. 6

    Gestire gli errori e testare su emulatori

    Su alcuni dispositivi (o dopo aggiornamenti dell'OS) la lettura può fallire per chiavi corrotte. Avvolgi le operazioni in un try/catch e in caso di errore fai una pulizia difensiva.

    Attenzione: su alcuni emulatori Android più vecchi il Keystore può comportarsi in modo instabile; testa sempre anche su un dispositivo reale.

    Future<String?> readTokenSafe() async {
      try {
        return await SecureStorageService.instance.readToken();
      } on Exception catch (e) {
        // Chiave corrotta o Keystore non disponibile: puliamo e ripartiamo
        await SecureStorageService.instance.clearAll();
        return null;
      }
    }

    Risultato atteso

    L'app non crasha in caso di errore di decifratura e gestisce lo stato in modo pulito.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!