Armazenando suas chaves secretas no Flutter

Tempo de leitura: 2 minutes

A maneira mais segura de proteger suas informações confidenciais é não incluí-las em seu aplicativo. Mesmo em desenvolvimento nativo (Android, por exemplo), são necessárias etapas extras para impedir que alguém recupere as chaves, desmontando o programa. Estou escrevendo isso porque enfrentei o mesmo problema no Flutter, como outras pessoas, que procuravam algo semelhante a propriedades locais no Android.

A abordagem mais recomendada que encontrei é usar recursos de texto. No Flutter você só precisa carregar seu arquivo contendo suas chaves secretas como se estivesse carregando qualquer outro ativo.

Por exemplo,

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;

Future<String> loadAsset() async {
  return await rootBundle.loadString('assets/config.json');
}

Como é mostrado aqui nos documentos do Flutter: https://flutter.io/assets-and-images/#loading-text-assets

Esse é o caminho mais curto. Você pode carregar um JSON, analisá-lo com dart:convert e ter suas chaves. Mas para este exemplo, vamos torná-lo um pouco mais elaborado.

Primeiro, vamos criar um arquivo chamado secrets.json que manterá nossas chaves de API secretas. E armazená-lo no diretório raiz do nosso projeto. Lembre-se de não submeter seu arquivo secrets.json ao controle de versão.

{
  "api_key": "random_api_key"
}

Em seguida, precisamos escrever uma entrada em pubspec.yaml apontando para nosso arquivo secreto.

assets:
  - secrets.json

Agora, vamos definir a classe que vai guardar nossas chaves, digamos que se chama Secret.

class Secret {
  final String apiKey;  Secret({this.apiKey = ""});  factory Secret.fromJson(Map<String, dynamic> jsonMap) {
    return new Secret(apiKey: jsonMap["api_key"]);
  }
}

Em seguida, um SecretLoader.

import 'dart:async' show Future;
import 'dart:convert' show json;
import 'package:flutter/services.dart' show rootBundle;class SecretLoader {
  final String secretPath;
  
  SecretLoader({this.secretPath});  Future<Secret> load() {
    return rootBundle.loadStructuredData<Secret>(this.secretPath,
        (jsonStr) async {
      final secret = Secret.fromJson(json.decode(jsonStr));
      return secret;
    });
  }
}

Eu usei loadStructuredData porque esta função já foi projetada para esse caso de uso, você só precisa enviar um parser.

Depois disso, você pode usar o SecretLoader assim:

Future<Secret> secret = SecretLoader(secretPath: "secrets.json").load();

A partir daí, cabe a você como usar esse conhecimento. Se estiver usando streams, você pode convertê-los em streams com .toStream(). Ou você pode usá-lo em sua árvore de widgets raiz com o FutureBuilder.