CHAPITRE 5.2

Stockage sécurisé avec Secure Storage

Sauvegarder des données sensibles de manière sécurisée dans votre application Flutter
Dans cette section, vous allez découvrir flutter_secure_storage, un package qui permet de stocker des données sensibles de manière chiffrée. Contrairement à SharedPreferences, Secure Storage chiffre les données avant de les sauvegarder, ce qui est essentiel pour les mots de passe, tokens d'authentification, et autres informations confidentielles. 🔒

5.2Stockage sécurisé avec Secure Storage

5.2.1 – Pourquoi sécuriser certaines données

🔒 Qu'est-ce qu'une donnée sensible ?

Une donnée sensible est une information qui, si elle est compromise, peut causer des préjudices à l'utilisateur ou à l'application. Ces données nécessitent une protection particulière.

Voici des exemples de données sensibles :

  • Mots de passe : Les mots de passe ne doivent jamais être stockés en clair
  • Tokens d'authentification : JWT, OAuth tokens, API keys
  • Informations bancaires : Numéros de carte, codes CVV (même si c'est rare dans une app mobile)
  • Données personnelles : Numéros de sécurité sociale, informations médicales
  • Clés de chiffrement : Clés privées, secrets d'application

⚠️ Pourquoi SharedPreferences n'est pas suffisant ?

SharedPreferences stocke les données en clair (non chiffrées). Cela signifie que :

  • Sur Android : Les données sont dans des fichiers XML lisibles
  • Sur iOS : Les données sont dans NSUserDefaults, accessibles si l'appareil est jailbreaké
  • Sur Web : Les données sont dans localStorage, accessibles via les outils de développement
Risques de sécurité :
Si vous stockez un token d'authentification dans SharedPreferences et que quelqu'un accède au système de fichiers (root sur Android, jailbreak sur iOS), il peut lire votre token et se faire passer pour vous. C'est un risque majeur de sécurité !

✅ Comment Secure Storage protège vos données

flutter_secure_storage utilise le stockage sécurisé natif de chaque plateforme :

  • Android : Utilise EncryptedSharedPreferences (chiffrement AES-256) ou KeyStore pour les clés
  • iOS : Utilise Keychain (chiffrement au niveau du système)
  • Web : Utilise le chiffrement avec des clés dérivées
Chiffrement :
Le chiffrement transforme vos données en texte illisible. Même si quelqu'un accède aux fichiers, il ne peut pas lire les données sans la clé de déchiffrement, qui est gérée de manière sécurisée par le système d'exploitation.

📊 Tableau comparatif : SharedPreferences vs Secure Storage

Critère
SharedPreferences
Secure Storage
Chiffrement
Non
Oui (AES-256)
Données sensibles
❌ Ne pas utiliser
✅ Recommandé
Types de données
String, int, bool, double, List
String uniquement
Performance
✓✓✓ Très rapide
✓✓ Rapide
Simplicité
✓✓✓ Très simple
✓✓ Simple

Tableau comparatif entre SharedPreferences et Secure Storage.

🤔 Quand utiliser Secure Storage ?

✅ Utilisez Secure Storage pour :

  • Mots de passe
  • Tokens d'authentification (JWT, OAuth)
  • API keys et secrets
  • Clés de chiffrement
  • Toute donnée confidentielle

❌ Utilisez SharedPreferences pour :

  • Préférences utilisateur (thème, langue)
  • Paramètres de l'application
  • Données non sensibles
  • Compteurs et statistiques
💡 Règle d'or :
Si la divulgation de la donnée pourrait causer un préjudice (accès non autorisé, vol d'identité, etc.), utilisez Secure Storage. Sinon, SharedPreferences est suffisant et plus performant.

5.2.2 – Installation de flutter_secure_storage

🎯 Objectif

Installer le package flutter_secure_storage dans votre projet Flutter et le configurer correctement.

💡 Installation pas à pas

Étape 1 : Ajouter la dépendance

Ouvrez le fichier pubspec.yaml de votre projet Flutter et ajoutez la dépendance flutter_secure_storage dans la section dependencies :

dependencies:
  flutter:
    sdk: flutter
  flutter_secure_storage: ^9.0.0
Version du package
La version peut varier. Pour obtenir la dernière version, consultez pub.dev/packages/flutter_secure_storage ou utilisez la commande :
flutter pub add flutter_secure_storage
Cette commande ajoute automatiquement la dernière version compatible.

Étape 2 : Installer le package

Exécutez la commande suivante dans votre terminal, à la racine de votre projet Flutter :

flutter pub get
✅ Résultat attendu : Vous devriez voir un message indiquant que le package a été installé avec succès.

Étape 3 : Configuration Android (optionnel mais recommandé)

Pour Android, vous pouvez configurer le niveau de sécurité minimum dans android/app/build.gradle :

android {
    defaultConfig {
        minSdkVersion 18  // Au minimum 18 pour flutter_secure_storage
    }
}
Configuration iOS :
Sur iOS, aucune configuration supplémentaire n'est nécessaire. Le package utilise automatiquement le Keychain d'iOS, qui est sécurisé par défaut.

Étape 4 : Importer le package

Dans le fichier Dart où vous souhaitez utiliser Secure Storage, ajoutez l'import en haut du fichier :

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

📖 Vérifier l'installation

Pour vérifier que le package est correctement installé, créez un fichier de test et essayez d'importer le package :
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

void main() {
  print('flutter_secure_storage est prêt à être utilisé !');
}
Si aucune erreur n'apparaît, l'installation est réussie. ✅
💡 Astuce
Si vous utilisez VS Code ou Android Studio, l'IDE vous proposera automatiquement d'importer le package lorsque vous taperez FlutterSecureStorage dans votre code.

5.2.3 – Stocker des données sensibles

Pour stocker des données sensibles avec Secure Storage, vous devez créer une instance de FlutterSecureStorage et utiliser la méthode write().

📝 Qu'est-ce que la sauvegarde sécurisée ?

La sauvegarde sécurisée consiste à stocker des données de manière chiffrée. Les données sont automatiquement chiffrées avant d'être écrites sur le disque, et déchiffrées lors de la lecture. Secure Storage gère automatiquement le chiffrement et le déchiffrement pour vous.

📝 Syntaxe

Voici comment créer une instance et sauvegarder des données :

// Créer une instance de FlutterSecureStorage
final storage = FlutterSecureStorage();

// Sauvegarder une donnée (opération asynchrone)
await storage.write(key: 'token', value: 'mon_token_secret');
await storage.write(key: 'password', value: 'mon_mot_de_passe');

Analysons cette syntaxe :

  • FlutterSecureStorage() : Crée une instance de Secure Storage
  • write(key: 'token', value: '...') : Sauvegarde une donnée avec une clé (opération asynchrone)
  • key : Le nom de la clé (comme un identifiant)
  • value : La valeur à sauvegarder (doit être une String)
Pourquoi async/await ?
L'opération de chiffrement et d'écriture sur le disque est asynchrone. C'est pourquoi vous devez utiliser await et que votre fonction doit être async. 🔄

🧪 Exemple : Sauvegarder un token d'authentification

Voici un exemple complet qui sauvegarde un token d'authentification de manière sécurisée :

import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

void main() {
  runApp(const MonApp());
}

class MonApp extends StatelessWidget {
  const MonApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const MaPage(),
    );
  }
}

class MaPage extends StatefulWidget {
  const MaPage({super.key});

  @override
  State<MaPage> createState() => _MaPageState();
}

class _MaPageState extends State<MaPage> {
  final _tokenController = TextEditingController();
  final _storage = FlutterSecureStorage();

  Future<void> sauvegarderToken() async {
    // Sauvegarder le token de manière sécurisée
    await _storage.write(
      key: 'auth_token',
      value: _tokenController.text,
    );
    
    // Afficher un message de confirmation
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Token sauvegardé de manière sécurisée !'),
          backgroundColor: Colors.green,
        ),
      );
    }
  }

  @override
  void dispose() {
    _tokenController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sauvegarder avec Secure Storage'),
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              TextField(
                controller: _tokenController,
                decoration: const InputDecoration(
                  labelText: 'Token d\'authentification',
                  border: OutlineInputBorder(),
                  hintText: 'Entrez votre token',
                ),
                obscureText: true, // Masquer le texte saisi
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: sauvegarderToken,
                child: const Text('Sauvegarder de manière sécurisée'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Dans cet exemple, le token est sauvegardé de manière chiffrée. Même si quelqu'un accède au système de fichiers, il ne pourra pas lire le token en clair.

obscureText: true :
obscureText: true masque le texte saisi dans le TextField (utile pour les mots de passe et tokens). Les caractères sont remplacés par des points pour éviter qu'ils soient visibles à l'écran.

💾 Options de configuration

Vous pouvez configurer Secure Storage avec des options supplémentaires :

// Configuration avec options
final storage = FlutterSecureStorage(
  aOptions: AndroidOptions(
    encryptedSharedPreferences: true, // Utiliser EncryptedSharedPreferences
  ),
  iOptions: IOSOptions(
    accessibility: KeychainAccessibility.first_unlock_this_device,
  ),
);

// Sauvegarder avec des options spécifiques
await storage.write(
  key: 'token',
  value: 'mon_token',
  aOptions: AndroidOptions(
    encryptedSharedPreferences: true,
  ),
);
Important :
N'oubliez pas que les opérations avec Secure Storage sont asynchrones. Vous devez toujours utiliser await et déclarer votre fonction comme async.

💡 À propos de mounted :
Avant d'appeler setState ou d'afficher un SnackBar après une opération asynchrone, vérifiez toujours if (mounted). Cela permet d'éviter des erreurs si le widget a été retiré de l'arbre Flutter.

5.2.4 – Lire et supprimer des données sécurisées

Pour lire des données sécurisées, vous utilisez la méthode read(). Pour supprimer, vous utilisez delete() ou deleteAll().

📖 Lire des données sécurisées

La méthode read() déchiffre et retourne la valeur stockée. Elle retourne null si la clé n'existe pas.

📝 Syntaxe

// Créer une instance
final storage = FlutterSecureStorage();

// Lire une donnée (retourne null si la clé n'existe pas)
String? token = await storage.read(key: 'auth_token');

// Avec une valeur par défaut
String token = await storage.read(key: 'auth_token') ?? 'Aucun token';

// Lire toutes les clés
Map<String, String> allData = await storage.readAll();

Analysons cette syntaxe :

  • read(key: 'auth_token') : Lit et déchiffre la valeur associée à la clé
  • readAll() : Lit toutes les données stockées (retourne une Map)
  • ?? : Opérateur qui fournit une valeur par défaut si le résultat est null

🗑️ Supprimer des données sécurisées

Pour supprimer une clé spécifique ou toutes les données :

// Supprimer une clé spécifique
await storage.delete(key: 'auth_token');

// Supprimer toutes les données
await storage.deleteAll();

🧪 Exemple : Lire et supprimer un token

Voici un exemple complet qui lit et supprime un token d'authentification :

Exemple Secure Storage Flutter
Exemple Secure Storage Flutter avec lecture et suppression du token.
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

void main() {
  runApp(const MonApp());
}

class MonApp extends StatelessWidget {
  const MonApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const MaPage(),
    );
  }
}

class MaPage extends StatefulWidget {
  const MaPage({super.key});

  @override
  State<MaPage> createState() => _MaPageState();
}

class _MaPageState extends State<MaPage> {
  final _storage = FlutterSecureStorage();
  final _tokenController = TextEditingController();
  String tokenAffiche = 'Aucun token';

  @override
  void initState() {
    super.initState();
    chargerToken();
  }

  // Charger le token au démarrage
  Future<void> chargerToken() async {
    String? token = await _storage.read(key: 'auth_token');
    setState(() {
      tokenAffiche = token ?? 'Aucun token sauvegardé';
      _tokenController.text = token ?? '';
    });
  }

  // Sauvegarder le token modifié
  Future<void> sauvegarderToken() async {
    if (_tokenController.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Le token ne peut pas être vide'),
          backgroundColor: Colors.red,
        ),
      );
      return;
    }
    
    await _storage.write(
      key: 'auth_token',
      value: _tokenController.text,
    );
    
    chargerToken(); // Recharger l'affichage
    
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Token sauvegardé'),
          backgroundColor: Colors.green,
        ),
      );
    }
  }

  // Supprimer le token
  Future<void> supprimerToken() async {
    await _storage.delete(key: 'auth_token');
    chargerToken(); // Recharger l'affichage
    
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Token supprimé'),
          backgroundColor: Colors.orange,
        ),
      );
    }
  }

  @override
  void dispose() {
    _tokenController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Lire et supprimer avec Secure Storage'),
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'Token actuel :',
                style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.grey[200],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Text(
                  tokenAffiche,
                  style: const TextStyle(fontSize: 16),
                ),
              ),
              const SizedBox(height: 24),
              TextField(
                controller: _tokenController,
                decoration: const InputDecoration(
                  labelText: 'Modifier le token',
                  border: OutlineInputBorder(),
                  hintText: 'Entrez un nouveau token',
                ),
                obscureText: true, // Masquer le texte saisi
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: sauvegarderToken,
                child: const Text('Sauvegarder le token'),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: chargerToken,
                child: const Text('Recharger le token'),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: supprimerToken,
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                ),
                child: const Text('Supprimer le token'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Dans cet exemple :

  • Le token est chargé au démarrage de l'application
  • L'utilisateur peut recharger le token en cliquant sur le bouton
  • L'utilisateur peut supprimer le token, qui sera retiré du stockage sécurisé
Vérifier si une clé existe :
Vous pouvez vérifier si une clé existe en essayant de la lire :
String? token = await storage.read(key: 'auth_token');
if (token != null) {
  print('Token trouvé : $token');
} else {
  print('Aucun token sauvegardé');
}
Important :
Comme la méthode read() peut retourner null, utilisez toujours l'opérateur ?? pour fournir une valeur par défaut. Cela évite les erreurs si la clé n'existe pas encore.

5.2.5 – Différences avec SharedPreferences

Secure Storage et SharedPreferences sont deux solutions de stockage différentes, chacune adaptée à des cas d'usage spécifiques. Comprendre leurs différences vous aidera à choisir la bonne solution.

🔐 Sécurité

Secure Storage

  • ✅ Chiffrement AES-256
  • ✅ Utilise KeyStore (Android) / Keychain (iOS)
  • ✅ Protection au niveau du système
  • ✅ Même avec accès root/jailbreak, les données restent protégées

SharedPreferences

  • ❌ Pas de chiffrement
  • ❌ Données en clair dans les fichiers
  • ❌ Accessibles si accès au système de fichiers
  • ❌ Ne pas utiliser pour des données sensibles

📊 Types de données

SharedPreferences peut stocker plusieurs types :

  • String
  • int
  • double
  • bool
  • List<String>

Secure Storage ne peut stocker que :

  • String uniquement
Pourquoi seulement String ?
Secure Storage chiffre les données, et le chiffrement fonctionne sur des chaînes de caractères. Si vous avez besoin de stocker un int ou un bool, vous devez les convertir en String :
// Convertir en String avant de sauvegarder
await storage.write(key: 'age', value: age.toString());
await storage.write(key: 'isPremium', value: isPremium.toString());

// Convertir en int/bool lors de la lecture
int age = int.parse(await storage.read(key: 'age') ?? '0');
bool isPremium = (await storage.read(key: 'isPremium') ?? 'false') == 'true';

⚡ Performance

SharedPreferences est généralement plus rapide car :

  • Pas de chiffrement/déchiffrement
  • Accès direct aux fichiers
  • Moins de surcharge

Secure Storage est légèrement plus lent car :

  • Chiffrement avant écriture
  • Déchiffrement après lecture
  • Accès au KeyStore/Keychain
💡 En pratique :
La différence de performance est généralement négligeable pour la plupart des applications. La sécurité prime sur la performance pour les données sensibles.

📋 Tableau comparatif complet

Critère
SharedPreferences
Secure Storage
Chiffrement
Non
Oui (AES-256)
Types de données
String, int, double, bool, List
String uniquement
Performance
✓✓✓ Très rapide
✓✓ Rapide
Données sensibles
❌ Ne pas utiliser
✅ Recommandé
Simplicité
✓✓✓ Très simple
✓✓ Simple
Cas d'usage
Préférences, paramètres
Tokens, mots de passe

Légende : ✓✓✓ Excellent | ✓✓ Très bon | ✓ Bon | ✗ Faible

🤔 Quand utiliser chaque solution ?

✅ Utilisez SharedPreferences pour :

  • Thème (clair/sombre)
  • Langue de l'application
  • Volume sonore
  • Paramètres d'affichage
  • Compteurs et statistiques
  • Toute donnée non sensible

✅ Utilisez Secure Storage pour :

  • Tokens d'authentification
  • Mots de passe
  • API keys
  • Clés de chiffrement
  • Secrets d'application
  • Toute donnée sensible
💡 Bonne pratique :
Utilisez les deux solutions dans la même application ! SharedPreferences pour les préférences utilisateur, et Secure Storage pour les données sensibles. C'est la meilleure approche.

5.2.6 – Bonnes pratiques de sécurité

Utiliser Secure Storage est un bon début, mais il existe d'autres bonnes pratiques de sécurité à suivre pour protéger efficacement les données de vos utilisateurs.

🔒 Règles fondamentales

1. Ne jamais stocker de mots de passe en clair

Même avec Secure Storage, évitez de stocker les mots de passe directement. Utilisez plutôt des tokens d'authentification qui peuvent être révoqués.

⚠️ Important :
Si vous devez absolument stocker un mot de passe, utilisez Secure Storage. Mais idéalement, utilisez un système d'authentification qui génère des tokens au lieu de stocker les mots de passe.

2. Utiliser des noms de clés descriptifs mais non évidents

Utilisez des noms de clés clairs dans votre code, mais évitez des noms trop évidents qui pourraient révéler la nature des données :

// ✅ Bon : Nom descriptif mais pas trop évident
await storage.write(key: 'auth_token', value: token);
await storage.write(key: 'refresh_token', value: refreshToken);

// ❌ Éviter : Trop évident
await storage.write(key: 'password_123', value: password);
await storage.write(key: 'credit_card_number', value: cardNumber);

3. Valider les données avant de les stocker

Toujours valider les données avant de les sauvegarder :

Future<void> sauvegarderToken(String token) async {
  // Valider le token avant de le sauvegarder
  if (token.isEmpty) {
    throw Exception('Le token ne peut pas être vide');
  }
  
  if (token.length < 10) {
    throw Exception('Le token semble invalide');
  }
  
  // Sauvegarder seulement si valide
  await storage.write(key: 'auth_token', value: token);
}

4. Gérer les erreurs de manière appropriée

Utilisez try-catch pour gérer les erreurs potentielles :

Future<void> sauvegarderToken(String token) async {
  try {
    await storage.write(key: 'auth_token', value: token);
  } catch (e) {
    print('Erreur lors de la sauvegarde : $e');
    // Gérer l'erreur (afficher un message, logger, etc.)
  }
}

🛡️ Protection supplémentaire

1. Expiration des tokens

Stockez la date d'expiration avec le token et vérifiez-la avant utilisation :

// Sauvegarder le token avec sa date d'expiration
await storage.write(key: 'auth_token', value: token);
await storage.write(
  key: 'token_expires_at', 
  value: DateTime.now().add(Duration(hours: 24)).toIso8601String(),
);

// Vérifier l'expiration avant utilisation
String? token = await storage.read(key: 'auth_token');
String? expiresAt = await storage.read(key: 'token_expires_at');

if (token != null && expiresAt != null) {
  DateTime expiration = DateTime.parse(expiresAt);
  if (DateTime.now().isAfter(expiration)) {
    // Token expiré, le supprimer
    await storage.delete(key: 'auth_token');
    await storage.delete(key: 'token_expires_at');
    token = null;
  }
}

2. Nettoyer les données à la déconnexion

Supprimez toutes les données sensibles quand l'utilisateur se déconnecte :

Future<void> deconnexion() async {
  final storage = FlutterSecureStorage();
  
  // Supprimer toutes les données sensibles
  await storage.deleteAll();
  
  // Optionnel : Supprimer aussi les préférences non sensibles
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove('username'); // Si stocké dans SharedPreferences
}

3. Ne pas logger les données sensibles

Évitez de logger ou d'afficher les données sensibles dans la console :

// ❌ Ne pas faire
String token = await storage.read(key: 'auth_token');
print('Token : $token'); // DANGEREUX !

// ✅ Faire plutôt
String? token = await storage.read(key: 'auth_token');
if (token != null) {
  print('Token présent (${token.length} caractères)');
} else {
  print('Aucun token trouvé');
}

📋 Checklist de sécurité

Checklist avant de déployer :
  • ✅ Tous les mots de passe et tokens utilisent Secure Storage
  • ✅ Les données sensibles ne sont jamais loggées
  • ✅ Les tokens ont une expiration
  • ✅ Les données sont nettoyées à la déconnexion
  • ✅ Les erreurs sont gérées proprement
  • ✅ Les données sont validées avant stockage

🔐 Exemple complet : Gestion sécurisée d'authentification

Voici un exemple complet qui montre les bonnes pratiques :

import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class AuthService {
  final _storage = FlutterSecureStorage();
  
  // Sauvegarder un token avec expiration
  Future<void> sauvegarderToken(String token) async {
    try {
      // Valider le token
      if (token.isEmpty || token.length < 10) {
        throw Exception('Token invalide');
      }
      
      // Sauvegarder le token
      await _storage.write(key: 'auth_token', value: token);
      
      // Sauvegarder la date d'expiration (24 heures)
      final expiration = DateTime.now().add(Duration(hours: 24));
      await _storage.write(
        key: 'token_expires_at',
        value: expiration.toIso8601String(),
      );
    } catch (e) {
      print('Erreur lors de la sauvegarde du token : $e');
      rethrow;
    }
  }
  
  // Récupérer le token (vérifie l'expiration)
  Future<String?> getToken() async {
    try {
      String? token = await _storage.read(key: 'auth_token');
      String? expiresAt = await _storage.read(key: 'token_expires_at');
      
      if (token == null) {
        return null;
      }
      
      // Vérifier l'expiration
      if (expiresAt != null) {
        DateTime expiration = DateTime.parse(expiresAt);
        if (DateTime.now().isAfter(expiration)) {
          // Token expiré, le supprimer
          await deconnexion();
          return null;
        }
      }
      
      return token;
    } catch (e) {
      print('Erreur lors de la lecture du token : $e');
      return null;
    }
  }
  
  // Déconnexion : supprimer toutes les données
  Future<void> deconnexion() async {
    try {
      await _storage.deleteAll();
    } catch (e) {
      print('Erreur lors de la déconnexion : $e');
    }
  }
  
  // Vérifier si l'utilisateur est connecté
  Future<bool> estConnecte() async {
    String? token = await getToken();
    return token != null;
  }
}

Cet exemple montre :

  • Validation des données avant stockage
  • Gestion des erreurs avec try-catch
  • Expiration des tokens
  • Nettoyage à la déconnexion
  • Pas de logging des données sensibles
💡 Conseil final :
La sécurité est un processus continu. Restez informé des meilleures pratiques et mettez régulièrement à jour vos dépendances pour bénéficier des dernières corrections de sécurité.
Exercice pratique :x
Maintenant que vous maîtrisez Secure Storage, mettez vos connaissances en pratique avec l'exercice ci-dessous ! Vous allez créer une application d'authentification complète avec persistance sécurisée des identifiants.

🎯 Exercice pratique

Objectif : Créer une application d'authentification avec une interface élégante. L'application doit permettre de se connecter avec un nom d'utilisateur et un mot de passe. Les identifiants doivent être sauvegardés de manière sécurisée avec Secure Storage. Si l'utilisateur est déjà authentifié, il doit être redirigé automatiquement vers la page d'accueil au démarrage de l'application.

Interface d'authentification à reproduire
Interface à reproduire : Créez cette interface d'authentification avec persistance sécurisée en utilisant Secure Storage et les techniques apprises dans ce chapitre.

📝 Instructions :

  1. Identifiez les défis : Observez les fonctionnalités requises et notez les défis techniques (par exemple : "Comment vérifier si l'utilisateur est déjà connecté au démarrage ?", "Comment sauvegarder le mot de passe de manière sécurisée ?", "Comment naviguer vers la page d'accueil après connexion ?", etc.)
  2. Notez vos solutions : Avant de regarder le code, essayez de noter comment vous résoudriez chaque défi avec Secure Storage (vérification dans initState(), sauvegarde après connexion, navigation conditionnelle, etc.)
  3. Comparez avec les solutions : Cliquez sur "Afficher le code" ci-dessous pour voir les solutions proposées et comparer avec vos notes. Analysez comment l'authentification et la persistance sont gérées.

🎯 Fonctionnalités à implémenter :

  • Interface de connexion avec TextField pour username et password (texte masqué)
  • Sauvegarde sécurisée du username et password avec Secure Storage
  • Vérification au démarrage : si les identifiants sont sauvegardés, redirection automatique vers la page d'accueil
  • Page d'accueil simple avec un message de bienvenue et un bouton de déconnexion
  • Déconnexion : supprimer les identifiants et retourner à l'écran de connexion
  • Gestion des erreurs (champs vides, identifiants incorrects)
Points clés de l'exercice :
  • Vérification au démarrage : _verifierAuthentification() est appelé dans initState() pour vérifier si l'utilisateur est déjà connecté
  • Navigation conditionnelle : Si les identifiants existent, redirection automatique vers la page d'accueil avec pushReplacement
  • Sauvegarde sécurisée : Les identifiants sont sauvegardés avec Secure Storage après une connexion réussie
  • Déconnexion : Le bouton de déconnexion supprime les identifiants et retourne à l'écran de connexion
  • Validation : Utilisation de Form et TextFormField pour valider les champs
  • Interface élégante : Design moderne avec Material Design 3, icônes, et espacements harmonieux