如何修复安卓应用中Firebase OTP验证及短信权限问题?
Hey Krishna, I’ve run into this exact issue with clients before—Google’s permission restrictions can throw a wrench in OTP flows, but Firebase has an official, policy-compliant workaround: the SMS Retriever API. Let’s walk through how to implement it step by step so your app can auto-retrieve OTPs without needing READ_SMS or RECEIVE_SMS permissions.
Step 1: Add the SMS Retriever API Dependency
First, update your module-level build.gradle (or build.gradle.kts) to include the required libraries:
dependencies { // Firebase Phone Auth implementation "com.google.firebase:firebase-auth:22.3.0" // SMS Retriever API implementation "com.google.android.gms:play-services-auth-api-phone:18.0.1" }
Sync your project after adding these to apply the changes.
Step 2: Generate Your App’s Signature Hash
The SMS Retriever API only listens for SMS messages that include a unique hash tied to your app’s signature and package name. To generate this hash:
- Create a helper class to fetch the signature hash:
import android.content.Context import android.content.pm.PackageManager import android.util.Base64 import android.util.Log import java.security.MessageDigest import java.security.NoSuchAlgorithmException class AppSignatureHelper(context: Context) { private val TAG = "AppSignatureHelper" private val appContext: Context = context.applicationContext fun getAppSignatures(): List<String> { val appSignatures = mutableListOf<String>() try { val packageName = appContext.packageName val packageInfo = appContext.packageManager.getPackageInfo( packageName, PackageManager.GET_SIGNATURES ) for (signature in packageInfo.signatures) { val md = MessageDigest.getInstance("SHA-256") md.update(signature.toByteArray()) val hash = Base64.encodeToString(md.digest(), Base64.NO_WRAP) appSignatures.add(hash) Log.d(TAG, "App signature hash: $hash") } } catch (e: PackageManager.NameNotFoundException) { Log.e(TAG, "Package not found", e) } catch (e: NoSuchAlgorithmException) { Log.e(TAG, "No such algorithm", e) } return appSignatures } }
- Call this class from your activity to get the hash (run this once in debug mode to note the value):
val signatureHelper = AppSignatureHelper(this) val signatures = signatureHelper.getAppSignatures() // Check Logcat for your hash—copy this, you’ll need it for your SMS template
⚠️ Important: Use the hash generated with your release signing key for production builds. The debug hash won’t work with your published app.
Step 3: Set Up a BroadcastReceiver to Capture OTP
Create a receiver that listens for the SMS Retriever’s success intent:
import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import com.google.android.gms.auth.api.phone.SmsRetriever import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.api.Status class SmsBroadcastReceiver : BroadcastReceiver() { private var otpListener: OtpListener? = null fun setOtpListener(listener: OtpListener) { this.otpListener = listener } override fun onReceive(context: Context?, intent: Intent?) { if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action) { val extras = intent.extras val status = extras?.get(SmsRetriever.EXTRA_STATUS) as Status when (status.statusCode) { CommonStatusCodes.SUCCESS -> { // Get SMS message contents val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String // Extract OTP from the message (adjust regex based on your SMS template) val otpPattern = "\\d{6}".toRegex() // Matches 6-digit OTP val otpMatch = otpPattern.find(message) otpMatch?.value?.let { otp -> otpListener?.onOtpReceived(otp) } } CommonStatusCodes.TIMEOUT -> { // SMS not received within 5 minutes otpListener?.onOtpTimeout() } } } } interface OtpListener { fun onOtpReceived(otp: String) fun onOtpTimeout() } }
Step 4: Register the Receiver and Start SMS Retriever
In your activity where you handle OTP verification:
- Register the broadcast receiver (preferably in
onStart()):
private lateinit var smsReceiver: SmsBroadcastReceiver override fun onStart() { super.onStart() smsReceiver = SmsBroadcastReceiver() smsReceiver.setOtpListener(object : SmsBroadcastReceiver.OtpListener { override fun onOtpReceived(otp: String) { // Auto-fill the OTP input field or pass it to Firebase binding.otpEditText.setText(otp) verifyOtpWithFirebase(otp) } override fun onOtpTimeout() { // Show error to user Toast.makeText(this@OtpActivity, "OTP timed out. Please request a new one.", Toast.LENGTH_SHORT).show() } }) val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION) registerReceiver(smsReceiver, intentFilter) } override fun onStop() { super.onStop() unregisterReceiver(smsReceiver) }
- Start the SMS Retriever before sending the OTP request to Firebase:
private fun startSmsRetriever() { val client = SmsRetriever.getClient(this) val task = client.startSmsRetriever() task.addOnSuccessListener { // Retriever started successfully—now send the OTP request to Firebase sendOtpToUser() } task.addOnFailureListener { // Failed to start retriever—handle error Toast.makeText(this, "Failed to start OTP retrieval", Toast.LENGTH_SHORT).show() } } private fun sendOtpToUser() { // Your existing Firebase Phone Auth code to send OTP val phoneNumber = binding.phoneEditText.text.toString() val options = PhoneAuthOptions.newBuilder(FirebaseAuth.getInstance()) .setPhoneNumber(phoneNumber) .setTimeout(60L, TimeUnit.SECONDS) .setActivity(this) .setCallbacks(object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { override fun onVerificationCompleted(credential: PhoneAuthCredential) { // Auto-verification completed (rare case) signInWithPhoneAuthCredential(credential) } override fun onVerificationFailed(e: FirebaseException) { // Handle verification failure Toast.makeText(this@OtpActivity, "Verification failed: ${e.message}", Toast.LENGTH_SHORT).show() } override fun onCodeSent( verificationId: String, token: PhoneAuthProvider.ForceResendingToken ) { // Save verification ID and resending token for later use storedVerificationId = verificationId resendToken = token } }) .build() PhoneAuthProvider.verifyPhoneNumber(options) }
Step 5: Update Your OTP SMS Template
Your OTP message must include your app’s signature hash at the end (no extra spaces). For example:
Your verification code is: 123456
ABC123xyz789
Replace ABC123xyz789 with the hash you generated earlier. If you’re using Firebase’s default SMS template, go to the Firebase Console → Authentication → Sign-in method → Phone → SMS template, and add the hash to the message.
Key Notes
- No Permissions Needed: The SMS Retriever API doesn’t require
READ_SMSorRECEIVE_SMS—it uses Google Play Services to safely retrieve the SMS without accessing your app’s SMS inbox. - Release Signing Hash: Always use the hash generated with your release key for production. If you use App Bundle, make sure to generate the hash from the upload key (not the app signing key if Google manages your signing).
- OTP Extraction: Adjust the regex in
SmsBroadcastReceiverto match your OTP length (e.g.,\\d{4}for 4-digit codes).
Once you implement this flow, your app will auto-retrieve OTPs while staying compliant with Google’s privacy policies. Let me know if you hit any snags with specific steps!
内容的提问来源于stack exchange,提问作者Krishna Chhabria

