You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase手机认证:onCodeSent()触发但Android设备未收到OTP

Firebase手机号认证:onCodeSent触发但收不到OTP短信

问题描述

在Android应用中基于Firebase实现手机号认证,验证流程可正常启动,UserRepositoryImpl类的onCodeSent()回调已触发,但设备始终无法收到包含OTP的短信。

已排查内容

  • 使用的手机号属于Firebase控制台「测试用手机号」列表
  • 号码格式完全匹配控制台显示(包含国家码和“+”前缀)
  • 设备可正常接收其他非认证类短信

相关代码片段

PhoneSignUpViewModel.kt

fun onSignUpClick(activity: Activity, phoneNumber: String) {
    viewModelScope.launch {
        isLoading = true

        // LOG: Check the number before sending to the repository
        Log.d("PhoneSignUpViewModel", "Phone number being sent to signUp: $phoneNumber")
        userRepository.signUp(phoneNumber) // No need to pass token here
            .collect { result ->
                when (result) {
                    is Result.Success -> {
                        // Update the shared state holder (verificationId and token are in result.data)
                        otpSharedStateHolder.otpVerificationData.value =
                            OtpVerificationData(
                                result.data.verificationId ?: "",
                                result.data.token
                            )
                        isLoading = false
                        _events.emit(Event.NavigateToOtpVerification)
                    }

                    is Result.Error -> {
                        showError = true
                        isLoading = false
                    }

                    is Result.Loading -> {
                        isLoading = true
                    }
                }
            }
    }
}

UserRepositoryImpl.kt

override fun signUp(
    phoneNumber: String,
    token: PhoneAuthProvider.ForceResendingToken?
): Flow<Result<User>> = callbackFlow {
    trySend(Result.Loading)

    launch {
        try {
            // LOG: Verify the number format received by the repository
            Log.d("UserRepositoryImpl", "Phone number received in signUp: $phoneNumber")
            val optionsBuilder = PhoneAuthOptions.newBuilder(firebaseAuth)
                .setPhoneNumber(phoneNumber)
                .setTimeout(60L, TimeUnit.SECONDS)
                .setCallbacks(object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
                    override fun onVerificationCompleted(credential: PhoneAuthCredential) {
                        // (Unlikely to happen for phone auth)
                        launch {
                            try {
                                firebaseAuth.signInWithCredential(credential).await()
                                val user = firebaseAuth.currentUser
                                trySend(
                                    Result.Success(
                                        User(
                                            phoneNumber = phoneNumber,
                                            id = user?.uid
                                        )
                                    )
                                )
                            } catch (e: Exception) {
                                trySend(
                                    Result.Error(
                                        Exception("Sign-in failed: ${e.message}")
                                    )
                                )
                            }
                        }
                    }

                    override fun onVerificationFailed(e: FirebaseException) {
                        Log.e(
                            "UserRepositoryImpl",
                            "onVerificationFailed: ${e.message}",
                            e
                        )
                        trySend(Result.Error(e))
                    }

                    override fun onCodeSent(
                        verificationId: String,
                        forceResendingToken: PhoneAuthProvider.ForceResendingToken
                    ) {
                        Log.d("UserRepositoryImpl", "onCodeSent: $verificationId")
                        trySend(
                            Result.Success(
                                User(
                                    phoneNumber = phoneNumber,
                                    verificationId = verificationId,
                                    token = forceResendingToken
                                )
                            )
                        )
                        close() // Close the callbackFlow
                    }
                })

            if (token != null) {
                optionsBuilder.setForceResendingToken(token)
            }

            val options = optionsBuilder.build()
            PhoneAuthProvider.verifyPhoneNumber(options)

        } catch (e: Exception) {
            trySend(Result.Error(e))
        }
    }

    awaitClose { /* Clean up resources if needed */ }
}.catch { emit(Result.Error(it as Exception)) }

Logcat输出

  • D/PhoneSignUpViewModel: Phone number being sent to signUp: +16505550100
  • D/UserRepositoryImpl: Phone number received in signUp: +16505550100
  • D/UserRepositoryImpl: onCodeSent: [VERIFICATION_ID]

已尝试操作

  • 遵循Firebase手机号认证文档配置(对Play Integrity和App Check存在疑惑)
  • 在Firebase控制台添加了调试令牌

技术栈

  • Kotlin
  • Jetpack Compose
  • Hilt
  • Firebase Authentication
  • Play Integrity API(已配置但理解不深)

解决方案

1. 直接使用测试号码的预设OTP

Firebase控制台添加的测试手机号无需接收实际短信,每个测试号码对应固定的预设验证码,可直接在控制台「测试用手机号」列表中查看该值,输入即可完成验证。

2. 排查Play Integrity/App Check配置

  • 若开启了App Check,确认调试设备的调试令牌已正确添加到Firebase控制台,且App Check规则未拦截认证请求;可暂时关闭App Check(仅调试阶段)测试,若恢复正常,再重新配置Play Integrity验证规则。
  • 检查Play Integrity的配置状态,确保应用签名、包名与控制台配置完全匹配,避免因验证失败导致短信发送被阻断。

3. 检查短信模板合规性

确认Firebase认证控制台的短信模板符合运营商规范,调试阶段可直接使用Firebase默认模板测试,避免自定义模板因内容违规被拦截。

4. 设备环境排查

  • 确保设备网络可正常访问Firebase服务,部分地区运营商可能拦截国际短信(如+1开头的测试号),可尝试切换网络环境(如使用VPN)或添加本地测试号码验证。
  • 检查设备是否开启了短信过滤功能,排除系统或第三方应用拦截认证短信的可能。

内容的提问来源于stack exchange,提问作者Aashish Ace

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 04:47:11