I am tring to enable OTP verification from Google Authenticator or other Authenticator app. I was able to generate secretKey and place on QRcode. Correctly display on Authenticator app, but when I am trying to verify the OTP with the digits I get from the APP I get Incorrect ORP Code, and if I print "generatedCode" it is different from the one in the app. Here is my whole code:
class OtpQrCode extends StatefulWidget {
@override
_OtpQrCodeState createState() => _OtpQrCodeState();
}
class _OtpQrCodeState extends State<OtpQrCode> {
bool _isOtpEnabled = false;
String? _otpSecret;
final _formKey = GlobalKey<FormState>();
String? _otpCode;
void _toggleOtp() {
setState(() {
_isOtpEnabled = !_isOtpEnabled;
if (_isOtpEnabled) {
_otpSecret = OTP.randomSecret();
} else {
_otpSecret = null;
}
});
}
final TextEditingController _otpController = TextEditingController();
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SwitchListTile(
title: Text('Enable OTP'),
value: _isOtpEnabled,
onChanged: (bool value) {
_toggleOtp();
},
),
if (_isOtpEnabled)
QrImage(
data: _otpUri(),
version: QrVersions.auto,
size: 200,
),
if (_isOtpEnabled)
Text(_otpSecret ?? ''),
if (_isOtpEnabled)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _otpController,
maxLength: 6,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Enter OTP',
),
validator: (value) {
if (value!.isEmpty || value.length != 6) {
return 'Please enter a valid 6-digit OTP';
}
return null;
},
onChanged: (value) {
setState(() {
_otpCode = value;
});
},
),
SizedBox(height: 16.0),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_verifyOtp();
}
},
child: Text('Verify OTP'),
),
],
),
),
),
],
),
);
}
String _otpUri() {
String issuer = 'YourApp';
String accountName = '[email protected]'; // Replace with user email or unique identifier
return _generateTOTPUrl(
secret: _otpSecret,
accountName: accountName,
issuer: issuer,
algorithm: 'SHA1',
digits: 6,
period: 30,
);
}
String _generateTOTPUrl({
@required String? secret,
@required String? accountName,
@required String? issuer,
String algorithm = 'SHA1',
int digits = 6,
int period = 30,
}) {
return 'otpauth://totp/${Uri.encodeComponent(accountName!)}?secret=$secret&issuer=${Uri.encodeComponent(issuer!)}&algorithm=$algorithm&digits=$digits&period=$period';
}
void _verifyOtp() async {
if (_otpController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Enter OTP')),
);
return;
}
bool isCodeValid = false;
int currentTime = DateTime.now().millisecondsSinceEpoch;
for (int i = -1; i <= 1; i++) {
int time = ((currentTime + (i * 30 * 1000)) / 1000).floor();
String generatedCode = OTP.generateTOTPCodeString(
_otpSecret!,
time,
algorithm: Algorithm.SHA1,
length: 6,
);
print(generatedCode);
if (_otpController.text == generatedCode) {
isCodeValid = true;
break;
}
}
if (isCodeValid) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('OTP verified successfully!')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Incorrect OTP code')),
);
}
}
}