How to get OTP from SMS - autofill
Asked Answered
U

5

20

I want to catch or read OTP of SMS messages automatically. I did some tests like this code :

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Demo Auto OTP'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  TextEditingController _textController = TextEditingController();
  String _error;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Multi-Factor-Authentication"),
        ),
        body: Form(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.min,
            children: [
              TextField(
                controller: _textController,
                autofillHints: [ AutofillHints.oneTimeCode ],
                keyboardType: TextInputType.visiblePassword,
                maxLength: 6,
                maxLengthEnforced: true,
                style: TextStyle(fontSize: 32),
              ),

              RaisedButton(
                child: Text("Verify"),
                onPressed: () => Navigator.of(context).pop(_textController.value.text),
              ),
            ],
          ),
        )
    );
  }
}

And this is the test SMS message : 12345 is your code to log in.

Flutter documentation for oneTimeCode: https://api.flutter.dev/flutter/services/AutofillHints/oneTimeCode-constant.html

Flutter Autofill : https://github.com/flutter/flutter/blob/7891006299/packages/flutter/lib/src/services/autofill.dart#L362

IOS : https://developer.apple.com/documentation/uikit/uitextcontenttype

Android : https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_SMS_OTP

Urticaceous answered 4/1, 2021 at 7:42 Comment(0)
M
29

NEW ANSWER (as of June '23): We will be using this package to autofill sms otp: https://pub.dev/packages/sms_autofill. Add it to your pubspec.yaml. First of all make sure the sms you are receiving in your android phones should contain '<#>' at the beginning of the sms and you app signature key at the end of the sms. An example sms should look like:

<#> You otp code is 543216 app_signature.

There are multiple ways to generate app signature key, you can also use the sms_autofill package to get your app signature key using the below code:

await SmsAutoFill().getAppSignature;

It will return you your app signature in a String format, just add it at the end of your sms. You can remove the above code or ignore it if you already have your app signature.

First lets draw the widget:

PinFieldAutoFill(
                        decoration: BoxLooseDecoration(
                          strokeColorBuilder: PinListenColorBuilder(Colors.black, Colors.black26),
                          bgColorBuilder: const FixedColorBuilder(Colors.white),
                          strokeWidth: 2,
                        ),
                        autoFocus: true,
                        cursor: Cursor(color: Colors.red, enabled: true, width: 1),
                        currentCode: '',
                        onCodeSubmitted: (code) {},
                        codeLength: 6,
                        onCodeChanged: (code) {
                          print(code);
                        },
                      ),

Then in your onInit() state listen for sms code:

await SmsAutoFill().listenForCode();

This will listen for the SMS with the code during 5 minutes and when received, autofill the above widget.

In your onDispose method do not forget to dispose the listener:

SmsAutoFill().unregisterListener();

OLD ANSWER: This answer can be used if someone wants to read an user's sms. Also, if you use the bottom implementation PlayStore might reject your app if you do not provide proper explanation on why you would like to read an user's sms. I hope I am not too late for people who are still looking for an answer to this question. I have used two packages https://pub.dev/packages/alt_sms_autofill and https://pub.dev/packages/pin_code_fields. Add these two packages to your pubspec.yaml file. Run 'flutter pub get' to download the packages.

In your otp screen import both packages:

import 'package:alt_sms_autofill/alt_sms_autofill.dart';
import 'package:pin_code_fields/pin_code_fields.dart';

After your AppState extends State put the following function to grab incoming SMS:

  TextEditingController textEditingController1;

  String _comingSms = 'Unknown';

  Future<void> initSmsListener() async {

    String comingSms;
    try {
      comingSms = await AltSmsAutofill().listenForSms;
    } on PlatformException {
      comingSms = 'Failed to get Sms.';
    }
    if (!mounted) return;
    setState(() {
      _comingSms = comingSms;
      print("====>Message: ${_comingSms}");
      print("${_comingSms[32]}");
      textEditingController1.text = _comingSms[32] + _comingSms[33] + _comingSms[34] + _comingSms[35]
          + _comingSms[36] + _comingSms[37]; //used to set the code in the message to a string and setting it to a textcontroller. message length is 38. so my code is in string index 32-37.
    });
  }

My incoming OTP Message Format looks like this: Your phone verification code is 625742. In the above function, it is listening to incoming sms and saving it to a string. After the sms has been received, I am setting the '625742' code to my textEditing controller by giving the index position of the code in the string which then sets the value to my PinFields which you will see later.

Call functions in your initState:

  @override
  void initState() {
    super.initState();
    textEditingController1 = TextEditingController();
    initSmsListener();
  }

You should dispose anything you are not using inside your dispose function:

  @override
  void dispose() {
    textEditingController1.dispose();
    AltSmsAutofill().unregisterListener();
    super.dispose();
  }

Then you need to put the pinfields in your build function or inside a column like this:

PinCodeTextField(
          appContext: context,
          pastedTextStyle: TextStyle(
            color: Colors.green.shade600,
            fontWeight: FontWeight.bold,
          ),
          length: 6,
          obscureText: false,
          animationType: AnimationType.fade,
          pinTheme: PinTheme(
            shape: PinCodeFieldShape.box,
            borderRadius: BorderRadius.circular(10),
            fieldHeight: 50,
            fieldWidth: 40,
            inactiveFillColor: Colors.white,
            inactiveColor: ColorUtils.greyBorderColor,
            selectedColor: ColorUtils.greyBorderColor,
            selectedFillColor: Colors.white,
            activeFillColor: Colors.white,
            activeColor: ColorUtils.greyBorderColor
          ),
          cursorColor: Colors.black,
          animationDuration: Duration(milliseconds: 300),
          enableActiveFill: true,
          controller: textEditingController1,
          keyboardType: TextInputType.number,
          boxShadows: [
            BoxShadow(
              offset: Offset(0, 1),
              color: Colors.black12,
              blurRadius: 10,
            )
          ],
          onCompleted: (v) {
            //do something or move to next screen when code complete
          },
          onChanged: (value) {
            print(value);
            setState(() {
              print('$value');
            });
          },
        ),

Make sure to set controller to the pinfield widget and after you receive the sms use string indexs to set the code to your textfield. See the below image for example. enter image description here

Megass answered 23/11, 2021 at 6:1 Comment(10)
Just a heads up, if you use this package please make sure before uploading your application to PlayStore read all terms n conditions about reading user sms. My app was rejected multiple times as I could not give them a proper reason why I was using the package.Megass
This is by far the best package out there for reading the sms code !Rowley
Is this method working for iOS?Hereabouts
It should work for both ios n android @AhmedNabil. If you try it out please comment to confirmMegass
Apple device by default recognises otp messages for user to easily paste it to otp input areaMegass
@Md.KamrulAmin , yes iOS autofill works by default, using pin_code_textfield package only is sufficient in iOS.Hereabouts
@Md.KamrulAmin So do you have any idea how we can fix this? Play store is rejecting mine too. I mean I can't use this in a live application? If not, please suggest an alternative too.Disney
@AshuPathak In that case you have to specify why you are trying to read a user's message. If it is just for OTP you will be rejected as Android suggests developers to use AppSignature in OTP sms so that your app can notice an otp message as soon as it reaches the user.Megass
@Md.KamrulAmin what should I do to handle this? Play Store rejected my app tooBoney
@NehalJaisalmeria If you are using this package only for otp autofill then google playstore will reject your app. Configure your otp message: <#> Your otp is 4232 <app_signature>. Then use otp_autofill package to fill it automatically. If you want to read messages and want to use this package, raise an issue to playstore and explain briefly why you want to use this package to read sms.Megass
M
5

You may use this package: https://pub.dev/packages/sms_autofill

But consider the following limits:

Android SMS constraint For the code to be receive, it need to follow some rules as describe here: https://developers.google.com/identity/sms-retriever/verify

Be no longer than 140 bytes Begin with the prefix <#> Contain a one-time code that the client sends back to your server to complete the verification flow End with an 11-character hash string that identifies your app One example of SMS would be:

<#> ExampleApp: Your code is 123456 FA+9qCX9VSu

Mora answered 4/1, 2021 at 7:58 Comment(1)
Yes, iOS is supported and it's easier than Android, check the package.Mora
A
3

@Kamrul Hasan Jony described very well. But one thing that should be keep in mind who is implementing this. Before assigning the OTP to controller you should check app_Signature or another validation so that OTP takes from specific msg only. eg.

setState(() {
  _commingSms = commingSms;
  String aStr = _commingSms.replaceAll(new RegExp(r'[^0-9]'),'');
  String otp = aStr.substring(0,4);
  if(_commingSms.contains(AppConstants.appSignature)){
    textEditingController.text = otp;
    //_presenter.validateOtp(widget.apiToken, widget.phone, textEditingController.text, context);
  }
});

Moreover for iOS AltSmsAutofill().listenForSms returns iOS version(alt_sms_autofill: ^1.0.0). So you will get rid of everything if you use some kind of validation before assigning otp to the controller.

Adalie answered 6/2, 2022 at 8:33 Comment(0)
L
2

I have used this package for Recieveing SMS Check it

What it does was that it Listens for the SMS through it's listner, and When the SMS Arrives it print the SMS.

This was the Code, I wrote for this a while ago (I am not sure if the package have made some changes or updates, Coz I haven't been using it for a while, but it was working that time pefectly.),

SmsReceiver receiver = new SmsReceiver();
await receiver.onSmsReceived.listen((SmsMessage msg) => checkSMS(msg));

Method for printing the SMS body,

  checkSMS(SmsMessage msg) async {
    print(msg.body);
  }

Now you can Autofill the SMS and fetch out the OTP using some regex from the msg.body and set it to a TextFieldController text for autofill.

NOTE: It will fetch every SMS, So to fetch the only one you need you have to check the keyword or set some Regex at your side to show only the OTP message, or your company name in the message.

Lynlyncean answered 4/1, 2021 at 8:4 Comment(2)
@khaled09909 It should auto Work for IOS, Coz this is iOS built-in feature. Just autofocus the input field, and It may fill automatically. Plz Correct me if I am wrong.Lynlyncean
@khaled09909 #65563273 visit this link for more detailsLynlyncean
P
1

In Flutter, I used https://pub.dev/packages/sms_autofill package for read the OTP - and the code is

Pinput(
   androidSmsAutofillMethod:AndroidSmsAutofillMethod.smsRetrieverApi,
   listenForMultipleSmsOnAndroid:true,
   controller: otpController,
   focusNode: otpFocus,
   pinputAutovalidateMode:PinputAutovalidateMode.onSubmit,
   inputFormatters: [
    FilteringTextInputFormatter.digitsOnly
   ],
)

and I called this function in the login page var appSignatureID = await SmsAutoFill().getAppSignature; i added this code and used this appSignatureID variable in a Text widget to show the value in UI - and added that release AAB Bundle in internal testing, so I can get the app signature ID in the UI and I noted it. after that, I removed the code from showing in UI and send the noted key to the backend for the sms template

Polyester answered 12/5, 2023 at 6:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.