Automatic sms authentication without asking permission-Google’s SMS retriever API

Automatic sms authentication without asking permission-Google’s SMS retriever API

Since we want to use all the latest features in the Android framework, we always need to use the latest version. As Android is advancing to restrict sensitive user data that forces applications to request permissions, the downside is that it's not reliable because users can deny and our features, that depends on permission, can never be reached until they allow the application to do something even if we explain it.

An example is when the app asks for permission in order to read sms, after the user filled out the phone number, to automatically pick up the sms pin code to log he in. If the user denies the permission and he can not see the entire content of the sms by pulling down the notification, he'll have to get out from your app just to memorize the code and come back to write it. Besides being annoying, he may not come back any more.

Therefore, Google created an API called SMS Retriever that handle this flow


Lets get started with today’s topic, Following is the process of making your app SMS retriever API ready.

Those are the requirements:

  • Android devices with Play services version 10.2+
  • BroadcastReceiver
  • Google auth dependency
  • Google gcm dependency

dependencies {

 //...

implementation "com.google.android.gms:play-services-gcm:16.0.0"

implementation "com.google.android.gms:play-services-auth:16.0.0"

}

SMS pattern (prefix and app hash suffix)

  • 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 (see Generating a one-time code)
  • End with an 11-character hash string that identifies your app
Example:- <#> Your ExampleApp code is: 123ABC78 FA+9qCX9VSu

Computing your app's hash string

Google Play services uses the hash string to determine which verification messages to send to your app. The hash string is made of your app's package name and your app's public key certificate. To generate the hash string:

1.   Get your app's public key certificate as a lower-case hex string. For example, to get the hex string from your keystore, type the following command:

2.  keytool -alias MyAndroidKey -exportcert -keystore MyProduction.keystore | xxd -p | tr -d "[:space:]"

3.   Append the hex string to your app's package name, separated by a single space.

4.   Compute the SHA-256 sum of the combined string. Be sure to remove any leading or trailing whitespace from the string before computing the SHA-256 sum.

5.   Base64-encode the binary value of the SHA-256 sum. You might need to decode the SHA-256 sum from its output format first.

6.   Your app's hash string is the first 11 characters of the base64-encoded hash.

The following command computes the hash string from your app's production keystore:

keytool -exportcert -alias MyAndroidKey -keystore MyProductionKeys.keystore | xxd -p | tr -d "[:space:]" | echo -n com.example.myapp `cat` | sha256sum | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11

Alternatively, you can get your app's hash string with the AppSignatureHelper class from the SMS retriever sample app. However, if you use the helper class, be sure to remove it from your app after you get the hash string. Do not use hash strings dynamically computed on the client in your verification messages.

Now we need to create the broadcastReceiver to receive the SMS (Here is where the app will get the sms content):

public class SMSBroadcastReceiver extends BroadcastReceiver {

   private Listener listener;

   public void injectListener(Listener listeners) {

       this.listener = listeners;

   }

   @Override

   public void onReceive(Context context, Intent intent) {

       if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {

           Bundle extras = intent.getExtras();

           Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);

           switch (status.getStatusCode()) {

               case CommonStatusCodes.SUCCESS:

                   // Get SMS message contents

                   String message = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);

                   if (listener != null) {

                       if (message != null && message.length() > 0) {

                           listener.onSMSReceived(message);

                       }

                   }

                   // }

                   // Extract one-time code from the message and complete verification

                   // by sending the code back to your server for SMS authenticity.

                   break;

               case CommonStatusCodes.TIMEOUT:

                   // Waiting for SMS timed out (5 minutes)

                   // Handle the error ...

                   if (listener != null) {

                       listener.onTimeOut();

                   }

                   break;

           }

       }

   }

}

Then, we need to inform this broadcast to android. We do this adding the receive tag inside the manifest.xml:

<?xml version="1.0" encoding="utf-8"?>

<manifest ...>

   <application

       //...

       <receiver android:name=". SMSBroadcastReceiver ">

           <intent-filter>

               <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>

           </intent-filter>

       </receiver>

   </application>

</manifest>

Now, You get the interface declare its callbacks.

public interface Listener {

   void onSMSReceived(String otp);

   void onTimeOut();

}  

Now, you get the sms retriever client and then start it and declare its callbacks (addOnSuccessListener and addOnFailureListener):

public class MainActivity extends AppCompatActivity {

   private Listener listener;

   private SMSBroadcastReceiver smsBroadcastReceiver;

   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_main);

       smsBroadcastReceiver = new SMSBroadcastReceiver();

       /*

First, You need the number of the user on which the OTP will be received,

you can get the user’s phone number through HintRequest in the onActivityResult().

        */

         /*HintRequest hintRequest = new HintRequest.Builder()

               .setPhoneNumberIdentifierSupported(true)

               .build();

       PendingIntent intent = Auth.CredentialsApi.getHintPickerIntent(

               apiClient, hintRequest);

       startIntentSenderForResult(intent.getIntentSender(), RESOLVE_HINT, null, 0, 0, 0);

       */

       requestHint();

   }

   private void requestHint() {

       try {

           SmsRetrieverClient client = SmsRetriever.getClient(MainActivity.this);

           Task<Void> task = client.startSmsRetriever();

           task.addOnSuccessListener(new OnSuccessListener<Void>() {

               @Override

               public void onSuccess(Void aVoid) {

                   // Successfully started retriever, expect broadcast intent

                   // ...

                   listener = new Listener() {

                       @Override

                       public void onSMSReceived(String otp) {

                       }

                       @Override

                       public void onTimeOut() {

                       }

                   };

                   smsBroadcastReceiver.injectListener(listener);

                   registerReceiver(smsBroadcastReceiver, new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION));

               }

           });

           task.addOnFailureListener(new OnFailureListener() {

               @Override

               public void onFailure(@NonNull Exception e) {

                   // Failed to start retriever, inspect Exception for more details

                   // ...

               }

           });

       } catch (Exception e) {

           e.printStackTrace();

       }

   }

   @Override

   protected void onDestroy() {

       super.onDestroy();

       unregisterReceiver(smsBroadcastReceiver);

   }

   /*

In onActivityResult() you will receive the result of what user has selected.

If you do not get the number you can explicitly ask the user to enter the number to complete the registration flow

    */

   /*if (requestCode == RESOLVE_HINT) {

       if (resultCode == RESULT_OK) {

           Credential credential= data.getParcelableExtra(Credential.EXTRA_KEY);

           // credential.getId(); <-- will need to process phone number string

       }

   }*/

}

Now users can log in through sms pin code without permission!! =)

The example project can be found on (GITHUB)

If this article help you & you enjoyed this article, then please share it & hit the claps as much as you like.

Thanks.....


Michael Egner

Business Student

7 个月

? .

回复
T SAI DEVAKAR

Hum filal ADs seekh rahe hai

1 年

Hey Rohit Chaddha Sir Can you suggest me a way on how I can remove the permissions of SMS retriever for a given app?

Amit S.

Dynamic Full Stack Engineer with proven expertise in building high-performance Web and Mobile Applications, Cloud solutions, IT Consulting, and leveraging Generative AI to drive innovation and efficiency

5 年

Good Article. Keep it up

要查看或添加评论,请登录

社区洞察