I have implemented 2-factor authentication in my application, but I am unable to verify the OTP generated inside my app and from the Google Authenticator app, the OTPs generated in my app are very different from the Authenticator app.
here is how I am generating the OTP inside my application:
var otpStream = Stream<dynamic>.periodic(
const Duration(seconds: 1),
(val) => OTP.generateTOTPCodeString(
'JBSWY3DPEHPK3PXP', DateTime.now().millisecondsSinceEpoch,
length: 6,
interval: 30,
algorithm: Algorithm.SHA256,
isGoogle: true)).asBroadcastStream();
here I am using a stream so that I can constantly listen to the OTP changes every second for development purposes
I am using this package to generate the OTP: https://pub.dev/packages/otp
when registering my application in google authenticator app I display a QR Code, the content for which is otpauth://totp/companyName?secret=JBSWY3DPEHPK3PXP&issuer=ClientID&period=30
The secret key is already in base32 which is accepted by the authenticator app.
please let me know if I am doing something wrong as this is the first time I am implementing such a feature.
Answer
Found a rookie mistake after spending some time working, found that the QR which I am displaying is not passing the proper hashing algorithm so I changed the OTP generator function to now have a proper Hashing algorithm and also the QR to now pass the proper algorithm
here is the changed Stream to listen to the OTP in application :
var otpStream = Stream<dynamic>.periodic(
const Duration(seconds: 0),
(val) => OTP.generateTOTPCodeString(
'JBSWY3DPEHPK3PXP', DateTime.now().millisecondsSinceEpoch,
length: 6,
interval: 30,
algorithm: Algorithm.SHA1,
isGoogle: true)).asBroadcastStream();
and the data for QR will be as follows: (Note* remove curly braces everywhere on the URI)
otpauth://totp/{CompanyName:clientID}?Algorithm=SHA1&digits=6&secret={yourbase32key}&issuer={CompanyName}&period=30
you can add a variable in the initState(() {});
to subscribe to the stream like so:
import "dart:async";
late StreamSubscription _twoFASubscription;
var _currentOtp;
@override
void initState() {
_twoFASubscription = otpStream.listen((event) {
setState(() {
_currentOtp = event;
});
});
super.initState();
}
also after done listening ot the OTP changes, don't forget to call the dispose()
method to cancel the subscription from the stream like so:
@override
void dispose() {
_twoFASubscription.cancel();
super.dispose();
}
TL;DR
make sure the arguments you pass to the QR is the same as the one you are using for generating OTP inside your application