QR Auto Login with Flutter

Mariano Zorrilla
Flutter Community
Published in
6 min readMar 29, 2023
QR Login App with Flutter
QR Login App with Flutter

Have you ever wondered how to login other devices without the need of emails, passwords, PIN and so much more? Apps like Discord, WhatsApp and more uses QR Codes to login from other logged devices and you can do it as well with Flutter

First Step: Enable Firebase

Make sure to enable Email/Password Sign-in Method

Yep, the easiest way to do it is to create a project with Firebase and setup your project for Web and Mobile (or the platforms you want to support and are supported by Firebase using Flutter). Make sure to check the current support for “Firebase Messaging”.

Setup Platforms

Each platform supported by Flutter needs an specific set of parameters and classes you need to create in order to enable Firebase Messaging, including Web… which has many little setups like including this “firebase-messaging-sw.js” file that you may miss reading the >>> Docs <<<.

Once you follow all the steps and install the Firebase (flutterfire) CLI, a new class called: “firebase_options.dart” will be auto generated and ready to use. This will allow you to quickly init Firebase inside your Flutter App:

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());
}

Firebase Functions

This step may sound a bit tricky and hard if you haven’t develop with Js, but is super straight forward and will help you achieve the trick.

Why are we using this method? Well… we need security because, a convenient way to login can’t compromise such an important part of our App, users, trust and so much more.

In order to achieve our goal, we’ll get the user ID and create a Custom Token to log our users:

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;
});
});

Once we push this Firebase Function to your project, it will be “listening” to your calls from the client side:

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
}

as you can see, all we need is the “UID” of the logged user and request a Token for it.

Once we have this data, is time to SEND THE PUSH (Notification)!

Push Notifications

There are a few ways to do it but, for the sake of this example, we’ll be using a Client Side logic to send the 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;
}
}

You’ll notice a “Constants.authKey” variable and, yes, you’ll need to get yours from the Firebase Console as Server key:

Bear in mind this implementation is “Legacy” and may not work in the future. Make sure to follow the latest Push Notification implementation when reading this post.

Once we get the token from previously, now we need to send it:

/.../ 
final customToken = reqToken.data as String;

final push = PushNotifications();
push.send(deviceToken: token, token: customToken);

Now, what is “deviceToken” you may ask… well, is the QR Code from the other Client device. The QR Code will be generated by the Token data that we’ll read from our logged device and send the Push Notification to the device waiting to be logged. Something like this:

Device Waiting -> Generates QR Code -> Waits for the Push Notification

Device Logged -> Reads QR Code -> Generates Token -> Send Push Notification with Custom Token

Sounds weird, but once implemented, the whole magic comes to life!

We’ll be using the “qr_flutter” plugin to show our 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,
)

and we’ll be getting to data to fill it + waiting for the Push Notification like
so:

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;
}

then the final touches and we’re good to go:

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

This method makes the entire “magic” happen. This comes from the Firebase Function we did and was requested by the logged device. Once we have the Device Token to target where to send the Push Notification and the Custom Token to login without credentials (manually), the entire process makes it look super simple!

QR Scan

As a final part of this demo, I’ll give you the snippet to Scan QR 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();
}
}

Once Scan a QR Code, this screen will dismiss and we can send the Push we know so much about it:

Future<void> _scanQrCode() async {
final token = await Navigator.of(context).push<String?>(
MaterialPageRoute(builder: (_) => const QrScanScreen()),
);

if (token != null) {
_sendPush(token);
}
}

THAT’S IT! We did it!

Congrats! Now you can add QR Login into any Flutter App with this “simple” (hopefully) example! Let’s see it in ACTION:

It’s working!!!

--

--

Mariano Zorrilla
Flutter Community

GDE Flutter — Tech Lead — Engineer Manager | Flutter | Android @ Venmo