Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🐛] Bug Report Title - Firebase Auth OTP verification , we are able to get successful for Production & Debug APK but not in play store aab Release ! #8208

Open
shreek219 opened this issue Jan 2, 2025 · 1 comment
Labels

Comments

@shreek219
Copy link

shreek219 commented Jan 2, 2025

Firebase Auth OTP Verification Issue on Play Store AAB Release

Description

We are experiencing an issue with Firebase Authentication OTP verification in our Android app downloaded via the Play Store (AAB Release). The OTP auto-read bypasses the manual entry in some scenarios (e.g., login), but it fails during the “Forgot Password” flow with the error below:

Here is the error , when we are trying to sign with auto read we are by passing OTP case but in Forget password screen we are getting below error!

Ignoring header X-Firebase-Locale because its value was null.
signInWithCredential:onComplete:failure com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The sms code has expired. Please re-send the verification code to try again.
In Android only i am facing this issue on iOS is working expected!

"react-native": "0.74.3",    
"@react-native-firebase/analytics": "^21.5.0",
"@react-native-firebase/app": "^21.5.0",
"@react-native-firebase/auth": "^21.5.0",
"@react-native-firebase/crashlytics": "^21.5.0",

This is code which used for OTP verification while we get OTP from Firebase/auth:
import React, {useState, useEffect, useRef} from 'react'
import {
KeyboardAvoidingView,
SafeAreaView,
StyleSheet,
Text,
View,
Alert,
TextInput,
} from 'react-native'
import {useNavigation} from '@react-navigation/native'
import {useSelector} from 'react-redux'
import CustomButton from '../../ui/CustomButton'
import {showErrorMessage} from '../../app-base/utils'
import {useTranslation} from 'react-i18next'
import {RootState} from '../../store/store'
import {replaceWithAsterisks} from '../../utilities/date'
import CustomHeader from '../../ui/CustomHeader'
import {WIDTH} from '../../theme'
import auth from '@react-native-firebase/auth'

const MobileVerificationPage = ({route}) => {
const navigation = useNavigation()
const [otpArray, setOtpArray] = useState(['', '', '', '', '', '']) // Each box as a separate state item
const [resendTimer, setResendTimer] = useState(30)
const [isResendDisabled, setIsResendDisabled] = useState(true)
const [isVerifyDisabled, setIsVerifyDisabled] = useState(true)
const [confirmation, setConfirmation] = useState(route?.params?.confirmation)
const [isLoading, setIsLoading] = useState(route?.params?.isLoading || false)
const authState = useSelector((state: RootState) => state.auth)
const number = replaceWithAsterisks(authState.mobileNumber || '1234567890')
const {t} = useTranslation()
const otpRefs = useRef<TextInput[]>([])

useEffect(() => {
const sendOtp = async () => {
try {
setIsLoading(true)
const confirmationResult = await auth().signInWithPhoneNumber(
'+' + authState.countryCode + authState.mobileNumber,
)
setConfirmation(confirmationResult)
Alert.alert('OTP Sent', 'An OTP has been sent to your phone number.')
} catch (error) {
handleError(error)
} finally {
setIsLoading(false)
}
}

if (!confirmation) sendOtp()

}, [authState.countryCode, authState.mobileNumber, confirmation])

useEffect(() => {
let timer = null
if (resendTimer > 0) {
timer = setTimeout(() => setResendTimer(resendTimer - 1), 1000)
} else if (resendTimer === 0 && isResendDisabled) {
setIsResendDisabled(false)
}
return () => timer && clearTimeout(timer)
}, [resendTimer, isResendDisabled])

const handleOtpBoxChange = (text, index) => {
const otpCopy = [...otpArray]

if (text.length > 1) {
  // Autofill scenario - when all 6 digits are filled at once
  const otpDigits = text.split('')
  setOtpArray(otpDigits)

  // Move focus to the last field (index 5)
  otpRefs?.current[5]?.focus()

  setIsVerifyDisabled(false)
} else {
  // Manual entry scenario - updating one digit at a time
  otpCopy[index] = text
  setOtpArray(otpCopy)

  // Move focus to the next box if text is entered
  if (text && index < otpArray.length - 1) {
    otpRefs.current[index + 1].focus()
  } else if (!text && index > 0) {
    otpRefs.current[index - 1].focus() // Move focus back if deleting
  }

  // Enable verify button only if all fields are filled
  setIsVerifyDisabled(otpCopy.some((digit) => digit === ''))
}

}

const onPress = async () => {
const otpInput = otpArray.join('')
if (otpInput.length < 6) {
showErrorMessage('Please fill in all fields.')
return
}

try {
  setIsLoading(true)
  await confirmation?.confirm(otpInput)
  navigation.navigate('CreatePassword')
} catch (error) {
  showErrorMessage('Invalid OTP, please try again.')
  setIsVerifyDisabled(true)
} finally {
  setIsLoading(false)
}

}

const handleResend = async () => {
if (isResendDisabled) return

try {
  setResendTimer(30)
  setIsResendDisabled(true)
  setIsVerifyDisabled(true)
  setIsLoading(true)

  const newConfirmation = await auth().signInWithPhoneNumber(
    '+' + authState.countryCode + authState.mobileNumber,
  )
  setConfirmation(newConfirmation)
  Alert.alert('OTP Resent', 'A new OTP has been sent to your phone number.')
} catch (error) {
  handleError(error)
} finally {
  setIsLoading(false)
}

}

const handleError = (error) => {
if (error.code === 'auth/invalid-phone-number') {
showErrorMessage('Please enter a valid phone number')
setIsVerifyDisabled(true)
} else {
showErrorMessage('You have reached the limit. Please try again later.')
}
}

return (


<CustomHeader
title={t('authPages.otpVerification')}
onBackPress={() => navigation.goBack()}
isNotification={false}
/>



{t('authPages.otpVerificationInfo')} {number}

{/* Visible OTP boxes */}

{otpArray.map((digit, index) => (
<TextInput
key={index}
style={styles.textInput}
value={digit}
onChangeText={(text) => handleOtpBoxChange(text, index)}
keyboardType="number-pad"
maxLength={index === 0 ? 6 : 1} // Allow first box to accept up to 6 digits
ref={(el) => (otpRefs.current[index] = el!)}
textContentType="oneTimeCode" // Autofill applies to the first input only
autoComplete={index === 0 ? 'sms-otp' : undefined}
/>
))}

      <View style={styles.footer}>
        <CustomButton
          title={t('mobileVerificationPage.Verify')}
          onPress={onPress}
          isFill
          style={[
            styles.verifyButton,
            isVerifyDisabled && styles.disabledButton,
          ]}
          disabled={isVerifyDisabled || isLoading}
        />
        <Text style={styles.textPrimary}>
          {t('authPages.didntGetOtp')}
          {isResendDisabled ? (
            <Text style={styles.disableText}>
              {t('authPages.resendSmsIn')} {`${resendTimer}s`}
            </Text>
          ) : (
            <Text onPress={handleResend} style={styles.resendLink}>
              {t('authPages.resendOtp')}
            </Text>
          )}
        </Text>
      </View>
    </KeyboardAvoidingView>
  </View>
</SafeAreaView>

)
}

const styles = StyleSheet.create({
container: {
backgroundColor: '#FFFFFF',
flex: 1,
},
main: {
height: '100%',
width: '100%',
justifyContent: 'space-between',
},
keyboardAvoidingView: {
flex: 1,
flexDirection: 'column',
},
inputContainer: {
paddingHorizontal: 8,
justifyContent: 'center',
alignItems: 'center',
},
label: {
fontSize: 14,
color: 'rgba(0, 20, 12, 0.40)',
textAlign: 'left',
lineHeight: 24,
marginVertical: 16,
},
hiddenInput: {
height: 0,
opacity: 0, // Hidden input to capture autofill
},
otpBoxesContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
width: '80%',
},
otpBox: {
height: 50,
width: WIDTH / 8,
fontSize: 24,
textAlign: 'center',
borderBottomWidth: 1,
borderColor: '#42BA85',
margin: 4,
},
footer: {
padding: 20,
alignItems: 'center',
},
verifyButton: {
marginBottom: 16,
width: '90%',
borderColor: 'gray',
borderWidth: 1,
},
disabledButton: {
opacity: 0.6,
},
resendLink: {
padding: 4,
color: '#6FCF97',
textDecorationLine: 'underline',
},
disableText: {
paddingLeft: 4,
color: 'rgba(0, 20, 12, 0.40)',
},
textPrimary: {
color: '#00140C',
fontSize: 14,
lineHeight: 24,
},
textInputContainer: {
margin: 4,
flexDirection: 'row',
justifyContent: 'space-between',
width: '90%',
},
textInput: {
height: WIDTH / 6 - 16,
width: WIDTH / 6 - 16,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
borderWidth: 1,
borderColor: '#42BA85',
backgroundColor: '#F4FBF8',
textAlign: 'center',
fontSize: 24,
margin: 4,
},
})

export default MobileVerificationPage

Issue:
We are facing an issue with Firebase Authentication OTP verification when the app is downloaded via the Play Store (AAB Release). OTP auto-read bypasses the manual entry on the login screen, but it fails during the “Forgot Password” flow with the following error:

Ignoring header X-Firebase-Locale because its value was null.
signInWithCredential:onComplete:failure com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The sms code has expired. Please re-send the verification code to try again.

This issue occurs only on Android (Play Store release), while the iOS version works as expected. The issue does not occur in Production or Debug APKs but only in the AAB released on Play Store.

Steps to Reproduce:
1. Open the app downloaded from the Play Store.
2. Navigate to the Forgot Password screen.
3. Enter the phone number to request an OTP.
4. Try to auto-read or manually input the OTP.
5. The app throws an error stating the OTP has expired, even if it’s correct and received recently.

Observations:
• The issue occurs only in the AAB Release on Play Store.
• iOS version is working as expected.
• The app was transferred from one Play Store account to another without updating SHA keys or configuration.

Relevant Details:
• Framework: React Native (0.74.3)
• Firebase Auth Library Version: @react-native-firebase/auth 21.5.0
• Other Firebase Packages:
• @react-native-firebase/analytics 21.5.0
• @react-native-firebase/app 21.5.0
• @react-native-firebase/crashlytics 21.5.0
• Firebase Configuration:
• All required SHA keys (Debug, Release, App Signing Certificate) are added to Firebase.

Let me know if additional details are needed!

I have attaced Firebase Sh Keys which added for play console and cloud console as well! Can you help for same ! @andymatuschak ! Already App is live ! and once more thing we transferred Application from One Play account to another play account ! but haven't update other changes!

AppSigning FirebaseSHKeys AppIntegrity
@mikehardy
Copy link
Collaborator

If it is working in apks you build but not the APK that play store builds from your AAB submission the only thing I can think of is you need to triple-check the SHAs used to sign the APK that comes down from play store built from your AAB.

Perhaps use https://www.apkmirror.com/ to get the APK, and then perhaps use certool to check SHA https://stackoverflow.com/questions/11331469/how-do-i-find-out-which-keystore-was-used-to-sign-an-app

I can only emphasize that if you assert you are able to make it once in one configuration that is a very strong indicator that this module in this repository works, but you have a configuration issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants