Read SMS automatically to verify OTP - Android, Kotlin

Crack the OTP Code: Easy SMS Reading Techniques for Android Developers

In today’s mobile-first world, seamless user experiences are crucial, especially regarding authentication. One common method is using One-Time Passwords (OTPs) sent via SMS. But how can you read these SMS messages and extract the OTP effortlessly in your Android app?

Photo by Onur Binay on Unsplash

In this blog, we’ll dive into various methods to read SMS in Android using Kotlin and show you how to implement them step-by-step.


Why Read SMS for OTP Verification?

Reading SMS messages allows you to streamline the login process by automatically retrieving the OTP sent to users. This enhances user experience by reducing manual input errors and speeding up authentication.


Method 1: Using the SMS Retriever API

The SMS Retriever API is a powerful tool provided by Google that allows you to receive SMS messages without needing SMS permissions. Let’s get started!

Credits: Google Developers

Step 1: Set Up Your Project

First, ensure you have the necessary dependencies in your build.gradle file:

dependencies {
    implementation 'com.google.android.gms:play-services-auth-api-phone:19.2.0'
}

Step 2: Start the SMS Retriever

In your activity, initialize the SMS Retriever:

import com.google.android.gms.auth.api.phone.SmsRetriever

fun startSmsRetriever() {
    val client = SmsRetriever.getClient(this)
    val task = client.startSmsRetriever()

    task.addOnSuccessListener {
        // SMS Retriever started successfully
    }.addOnFailureListener {
        // Failed to start SMS Retriever
    }
}

Step 3: Create a BroadcastReceiver

Create a BroadcastReceiver to handle incoming SMS messages. This receiver will extract the OTP for you.

class SmsBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action) {
            val extras: Bundle? = intent.extras
            val status = extras?.get(SmsRetriever.EXTRA_STATUS) as? Int

            if (status == SmsRetriever.RESULT_STATUS_OK) {
                val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as? String
                val otp = extractOtp(message)
                Log.d("SmsReceiver", "OTP received: $otp")
                // Use the OTP as needed
            }
        }
    }

    private fun extractOtp(message: String?): String? {
        return message?.split(" ")?.find { it.length == 6 && it.all { char -> char.isDigit() } }
    }
}

Step 4: Register the BroadcastReceiver

In your activity, register the BroadcastReceiver to listen for SMS:

override fun onStart() {
    super.onStart()
    val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
    registerReceiver(smsBroadcastReceiver, intentFilter)
}

override fun onStop() {
    super.onStop()
    unregisterReceiver(smsBroadcastReceiver)
}

Step 5: Send a Properly Formatted SMS

Make sure the SMS you send is formatted correctly, like this:

Your verification code is: 123456

This will ensure that the SMS Retriever can extract the OTP easily.


Method 2: Using a BroadcastReceiver to Read Incoming SMS

This method provides a straightforward way to read SMS messages with the necessary permissions while giving you control over incoming messages as they are received.


Step 1: Declare Permissions

Add the following permission to your AndroidManifest.xml:

<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>

Step 2: Create a BroadcastReceiver

Create a BroadcastReceiver that listens for incoming SMS messages:

class SmsReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == "android.provider.Telephony.SMS_RECEIVED") {
            val bundle: Bundle? = intent.extras
            val msgs = bundle?.get("pdus") as Array<*>
            for (msg in msgs) {
                val smsMessage = SmsMessage.createFromPdu(msg as ByteArray)
                val sender = smsMessage.displayOriginatingAddress
                val messageBody = smsMessage.messageBody
                Log.d("SmsReceiver", "Sender: $sender, Message: $messageBody")
                // Here you can extract and process the OTP if needed
            }
        }
    }
}

Step 3: Register the Receiver in Manifest

Register your BroadcastReceiver in the AndroidManifest.xml:

<receiver android:name=".SmsReceiver">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>

Step 4: Handle Permissions

Ensure you handle permissions, especially on devices running Android 6.0 (API level 23) and above:

private val REQUEST_CODE_RECEIVE_SMS = 101

private fun checkSmsPermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS)
        != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, 
            arrayOf(Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS), 
            REQUEST_CODE_RECEIVE_SMS)
    } else {
        // Permission granted; ready to receive SMS
    }
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_CODE_RECEIVE_SMS && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // Permission granted; ready to receive SMS
    }
}


Method 3: Using Content Resolver

If you need more control and want to read SMS messages directly, you can use a ContentResolver. This method requires READ_SMS permission.

Step 1: Declare Permissions

Add the following permission to your AndroidManifest.xml:

<uses-permission android:name="android.permission.READ_SMS"/>

Step 2: Request Permission

In your activity, check and request the permission:

private val REQUEST_CODE_READ_SMS = 100

private fun checkSmsPermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS)
        != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, 
            arrayOf(Manifest.permission.READ_SMS), 
            REQUEST_CODE_READ_SMS)
    } else {
        readSms()
    }
}

Step 3: Read SMS

Now you can read SMS messages using the ContentResolver:

private fun readSms() {
    val cursor = contentResolver.query(
        Uri.parse("content://sms/inbox"),
        null, null, null, null
    )

    cursor?.use {
        while (it.moveToNext()) {
            val id = it.getString(it.getColumnIndexOrThrow("_id"))
            val address = it.getString(it.getColumnIndexOrThrow("address"))
            val body = it.getString(it.getColumnIndexOrThrow("body"))
            Log.d("SMS", "ID: $id, Address: $address, Body: $body")
        }
    }
}

Step 4: Handle Permissions Result

Make sure to handle the permission result:

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_CODE_READ_SMS && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        readSms()
    }
}


Now you have three powerful methods to read SMS and verify OTPs in your Android application using Kotlin!


Key Differences

Below, we'll explain the key differences between these approaches to help you decide which best suits your application's needs.

Permissions Required:

  • SMS Retriever API: No permissions are needed to read SMS.
  • BroadcastReceiver: Requires both RECEIVE_SMS and READ_SMS permissions.
  • Content Resolver: Requires READ_SMS permission.

Privacy:

  • SMS Retriever API: Enhances user privacy; does not access all SMS.
  • BroadcastReceiver: Less privacy; intercepts all incoming SMS.
  • Content Resolver: Less privacy; accesses all SMS messages.

Implementation Complexity:

  • SMS Retriever API: Simpler for OTP retrieval.
  • BroadcastReceiver: Moderate complexity; requires handling permissions and managing received messages.
  • Content Resolver: More complex; needs permission handling and SMS management.

Real-Time Retrieval:

  • SMS Retriever API: Retrieves SMS only when specifically formatted for the API.
  • BroadcastReceiver: Captures SMS messages in real-time as they arrive.
  • Content Resolver: Can read all SMS messages from the inbox at any time.

SMS Format:

  • SMS Retriever API: Requires specific formatting (e.g., with a hash).
  • BroadcastReceiver: No specific format required; captures all incoming messages.
  • Content Resolver: No specific format required; can read any SMS message.

Use Case:

  • SMS Retriever API: Best for OTP verification and temporary messages.
  • BroadcastReceiver: Ideal for applications requiring immediate access to incoming SMS.
  • Content Resolver: Suitable for apps needing broader SMS access (e.g., messaging apps).

User Experience:

  • SMS Retriever API: Seamless; no interruptions for permission prompts.
  • BroadcastReceiver: This may interrupt user experience due to permission requests.
  • Content Resolver: This may interrupt user experience due to permission requests.

Message Filtering:

  • SMS Retriever API: Automatically filters messages based on predefined criteria.
  • BroadcastReceiver: Requires manual processing of intercepted messages.
  • Content Resolver: Requires manual filtering and processing of messages.


Summary

  • SMS Retriever API is ideal for OTP retrieval and authentication scenarios, providing a simple and privacy-friendly approach.
  • BroadcastReceiver captures incoming SMS messages in real-time but also requires permissions and processes all messages, which may only sometimes be desirable for user privacy.
  • Content Resolver offers broader access to SMS but requires more setup and user permissions, making it suitable for apps that need comprehensive SMS management.

Each method has strengths and weaknesses, so the best choice depends on your app’s specific needs and the user experience you want to provide!


References:


Thanks for reading this article. Hope you would have liked it!. Please share and subscribe to my blog to support.

Pragnesh Ghoda

A forward-thinking developer offering more than 8 years of experience building, integrating, and supporting android applications for mobile and tablet devices on the Android platform. Talks about #kotlin and #android

2 Comments

Please let us know about any concerns or query.

  1. Works fine while the app is running. Throws error when the app is closed. mListener.messageReceived(messageBody); throws Null object reference. Please help me

    ReplyDelete
    Replies
    1. I have republished the Article. Now you can choose from 3 different methodsm depends on your app’s specific needs.

      Delete
Previous Post Next Post

Contact Form