QR Auto Login com Flutter
Você já se perguntou como fazer login em outros dispositivos sem a necessidade de e-mails, senhas, PIN e muito mais? Aplicativos como Discord, WhatsApp e mais usam códigos QR para fazer login de outros dispositivos conectados e você também pode fazer isso com o Flutter
Conteudo
Primeira etapa: ativar o Firebase
Sim, a maneira mais fácil de fazer isso é criar um projeto com Firebase e configurá-lo para Web e Mobile (ou as plataformas que você deseja suportar e são suportadas pelo Firebase usando Flutter). Certifique-se de verificar o suporte atual para “Firebase Messaging”.
Plataformas de configuração
Cada plataforma suportada pelo Flutter precisa de um conjunto específico de parâmetros e classes que você precisa criar para habilitar o Firebase Messaging, incluindo Web… que tem muitas pequenas configurações como incluir este arquivo firebase-messaging-sw.js que você pode perder ao ler o >>> Docs <<<.
Depois de seguir todas as etapas e instalar a CLI do Firebase (flutterfire), uma nova classe chamada: “firebase_options.dart” será gerada automaticamente e estará pronta para uso. Isso permitirá que você inicie rapidamente o Firebase dentro do seu aplicativo Flutter:
import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'firebase_options.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform ); runApp(const MainApp()); }
Funções do Firebase
Esta etapa pode parecer um pouco complicada e difícil se você não desenvolveu com Js, mas é super direta e ajudará você a realizar o truque.
Por que estamos usando esse método? Bem… precisamos de segurança porque uma maneira conveniente de fazer login não pode comprometer uma parte tão importante do nosso aplicativo, usuários, confiança e muito mais.
Para atingir nosso objetivo, obteremos o ID do usuário e criaremos um token personalizado para registrar nossos usuários:
const functions = require("firebase-functions"); const admin = require('firebase-admin'); const serviceAccount = require("./qr-to-login-firebase-adminsdk.json"); admin.initializeApp({ credential: admin.credential.cert(serviceAccount) }); exports.getToken = functions.https.onCall(async (data, context) => { return admin .auth() .createCustomToken(data.uid) .then((customToken) => { return customToken; }).catch((error) => { return null; }); });
Assim que enviarmos esta Firebase Function para o seu projeto, ela estará “escutando” suas chamadas do lado do cliente:
final reqToken = await function.httpsCallable('getToken').call({'uid': user.uid}); if (reqToken.data != null) { final customToken = reqToken.data as String; // Do your logic to send the token }
como podem ver, só precisamos do “UID” do usuário logado e solicitar um Token para ele.
Assim que tivermos esses dados, é hora de ENVIAR O PUSH (Notificação)!
Notificações via push
Existem algumas maneiras de fazer isso, mas, para este exemplo, usaremos uma lógica do lado do cliente para enviar o push:
class PushNotifications { Future<bool> send({ String title = 'QrToLogin', String? deviceToken, String? token, }) async { const String url = 'https://fcm.googleapis.com/fcm/send'; final Map<String, dynamic> data = { 'notification': { 'title': title, }, 'priority': 'high', 'data': { 'status': 'done', 'click_action': 'FLUTTER_NOTIFICATION_CLICK', 'token': token }, 'to': '$deviceToken', 'message': { 'token': '$deviceToken' } }; final Map<String, String> headers = { 'Content-Type': 'application/json', 'Authorization': Constants.authKey, }; final result = await http.post( Uri.parse(url), body: jsonEncode(data), encoding: Encoding.getByName('utf-8'), headers: headers, ); return result.statusCode == 200; } Future<bool> sendToTopic(String id, { String topic = 'all', String title = 'QrToLogin', String token = '', }) async { const String url = 'https://fcm.googleapis.com/fcm/send'; final Map<String, dynamic> data = { 'notification': { 'title': title, }, 'priority': 'high', 'data': { 'id': id, 'status': 'done', 'click_action': 'FLUTTER_NOTIFICATION_CLICK', 'token': token, }, 'topic': topic, }; final Map<String, String> headers = { 'Content-Type': 'application/json', 'Authorization': Constants.authKey, }; final result = await http.post( Uri.parse(url), body: jsonEncode(data), encoding: Encoding.getByName('utf-8'), headers: headers, ); return result.statusCode == 200; } }
Você notará uma variável “Constants.authKey” e, sim, precisará obter a sua no Firebase Console como chave do servidor:
Lembre-se de que esta implementação é “Legado” e pode não funcionar no futuro. Certifique-se de seguir a implementação mais recente do Push Notification ao ler esta postagem.
Assim que obtivermos o token anteriormente, agora precisamos enviá-lo:
/.../ final customToken = reqToken.data as String; final push = PushNotifications(); push.send(deviceToken: token, token: customToken);
Agora, o que é “deviceToken” você pode perguntar… bem, é o QR Code do outro dispositivo do Cliente. O QR Code será gerado pelos dados do Token que iremos ler do nosso dispositivo logado e enviar o Push Notification para o dispositivo que está aguardando para ser logado. Algo assim:
Dispositivo aguardando -> Gera código QR -> Aguarda a notificação por push
Dispositivo registrado -> Lê código QR -> Gera token -> Enviar notificação por push com token personalizado
Parece estranho, mas uma vez implementado, toda a mágica ganha vida!
Estaremos usando o plugin “qr_flutter” para mostrar nosso QR Code:
QrImage( data: _qr, version: QrVersions.auto, embeddedImage: const AssetImage( 'assets/flutter_log.png', // optional ), embeddedImageStyle: QrEmbeddedImageStyle( size: const Size(55, 55), ), size: 250, )
e estaremos obtendo dados para preenchê-lo + aguardando a notificação push assim:
Future<String?> getToken() async { String token = ''; final messaging = FirebaseMessaging.instance; NotificationSettings settings = await messaging.requestPermission( alert: true, announcement: false, badge: true, carPlay: false, criticalAlert: false, provisional: false, sound: true, ); if (settings.authorizationStatus == AuthorizationStatus.authorized) { token = await FirebaseMessaging.instance.getToken(); } return token; }
em seguida, os toques finais e estamos prontos para ir:
Future<void> _generateQr() async { final push = WebPush(); final token = await push.getToken(); setState(() => _qr = token ?? ''); FirebaseMessaging.onMessage.listen((event) async { _load(); // change any UI loading state final auth = FirebaseAuth.instance; final token = event.data['token']; final login = await auth.signInWithCustomToken(token); // MAGIC! if (login.user != null) { Navigator.of(context).push( MaterialPageRoute(builder: (_) => const HomeScreen()), // Do anything ); } _load(); // change any UI loading state }); }
signInWithCustomToken
Este método faz toda a “mágica” acontecer. Isso vem da Firebase Function que fizemos e foi solicitada pelo dispositivo logado. Assim que tivermos o token do dispositivo para direcionar para onde enviar a notificação por push e o token personalizado para fazer login sem credenciais (manualmente), todo o processo parece super simples!
QR Scan
Como parte final desta demonstração, darei a você o trecho para QR Scan Codes:
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; class QrScanScreen extends StatefulWidget { const QrScanScreen({Key? key}) : super(key: key); @override _QrScanScreenState createState() => _QrScanScreenState(); } class _QrScanScreenState extends State<QrScanScreen> { final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); Barcode? result; QRViewController? controller; @override void reassemble() { super.reassemble(); if (Platform.isAndroid) { controller!.pauseCamera(); } else if (Platform.isIOS) { controller!.resumeCamera(); } } @override Widget build(BuildContext context) { return Scaffold( body: QRView( key: qrKey, onQRViewCreated: _onQRViewCreated, ), ); } void _onQRViewCreated(QRViewController controller) { this.controller = controller; controller.scannedDataStream.listen((scanData) { this.controller?.stopCamera(); Navigator.of(context).pop(scanData.code); }); } @override void dispose() { controller?.dispose(); super.dispose(); } }
Depois de digitalizar um QR Code, esta tela será descartada e podemos enviar o Push que sabemos tanto sobre isso:
Future<void> _scanQrCode() async { final token = await Navigator.of(context).push<String?>( MaterialPageRoute(builder: (_) => const QrScanScreen()), ); if (token != null) { _sendPush(token); } }