13

Whenever I tried to login with phone number using react-native-firebase sdk, I recieve OTP code through sms and when I submit the recieved code, an error is there saying:"The sms code has expired. Please re-send the verification code to try again." And here point to be noted that an entry for respective phone number is writing in Users section of firebase even there is an error.

I am using following:

NodeJS: v8.11.1,
NPM: v5.6.0,
react-native: "^0.59.9",
react-native-firebase: "^5.5.3"

Some links I have already tried are:

1. https://github.com/invertase/react-native-firebase- 
docs/blob/master/docs/auth/phone-auth.md
2. https://stackoverflow.com/questions/46522726/firebase-phone- 
authentication-error-the-sms-code-has-expired
3. https://www.bountysource.com/issues/45308170-firebase-phone- 
number-auth-integration
4. https://medium.com/@chrisbianca/getting-started-with-firebase- 
authentication-on-react-native-a1ed3d2d6d91
5. https://invertase.io/oss/react-native-firebase/v6/auth/phone- 
auth

In MobileRegistration.js:

navigateToOtpScreen() {
console.log("Phone number: ", "+91" + this.state.phoneNumber)
firebase.auth().signInWithPhoneNumber("+91" + 
this.state.phoneNumber)
.then(confirmResult => {
   this.setState({ confirmResult, message: 'Code has been sent!'})
   this.props.navigation.navigate('EnterOtp', { 'confirmResult': 
   confirmResult})
 })
 .catch(error => {
      alert(error.message);
      this.setState({ message: `Sign In With Phone Number Error: 
      ${error.message}` })
  });

};

In EnterOtp.js:

componentDidMount() {
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
    alert(JSON.stringify(user));
    if (user) {
        this.setState({
            user: user.toJSON()
        });
    } else {
        // User has been signed out, reset the state
        this.setState({
            user: null,
            message: '',
            otp: '',
            otp1: '',
            otp2: '',
            otp3: '',
            otp4: '',
            otp5: '',
            otp6: ''

        });
    }
});

}

componentWillUnmount() {
if (this.unsubscribe) this.unsubscribe();

}

verifyOTP = async () => {
const {
    confirmResult,
} = this.props.navigation.state.params;
let otp = this.state.otp + "" + this.state.otp1 + "" + this.state.otp2 + "" + this.state.otp3 + "" + this.state.otp4 + "" + this.state.otp5 + "" + this.state.otp6
if (confirmResult && otp.length) {

    alert(otp);
    confirmResult.confirm(otp).then((user) => {
            AsyncStorage.setItem('accessToken', confirmResult._verificationId);
            this.props.navigation.navigate('SetupCoverVideo');
            this.setState({
                message: 'Code Confirmed!'
            });
        })
        .catch(error => {
            alert(error.message) && this.setState({
                message: `Code Confirm Error: ${error.message}`
            })
        });
}

}

Expected Result: Code should be verified and an entry should be in Users section of firebase and navigate to SetupCoverVideo.

Actual Result: Facing an error saying: "The sms code has expired. Please re-send the verification code to try again." And here point to be noted that an entry for respective phone number is writing in Users section of firebase even there is an error.

I am wondering for the solution. Anyone please assist me.

Bilal Abdeen
  • 1,627
  • 1
  • 19
  • 41
Ranu Kumar
  • 131
  • 1
  • 1
  • 4

2 Answers2

23

Apparently, some recent versions of Android are smart enough to receive the SMS verification code and use it to authenticate the user. This authentication happens in the background while the user still receives the verification code in an SMS. When the user tries to enter the verification code, he/she gets a message that the verification code expired, because Android has already used it (in the background) and has already logged in the user! To double-check that, check the Firebase Console. You should find that this new user has been added to the list of users.

To avoid receiving the verification code expiry message, we need to set up a listener for "authentication changes." As soon as Android logs in the user in the background, this listener should navigate the user away from the login screen, in which he/she was supposed to enter the verification code. The following demonstrates how this can be implemented. I would add the following code to the login screen.

Example code for use with functional components:

useEffect( () => {
    firebase.auth().onAuthStateChanged( (user) => {
        if (user) {
            // Obviously, you can add more statements here, 
            //       e.g. call an action creator if you use Redux. 

            // navigate the user away from the login screens: 
            props.navigation.navigate("PermissionsScreen");
        } 
        else 
        {
            // reset state if you need to  
            dispatch({ type: "reset_user" });
        }
    });
}, []);  

Example code for use with class components:

// I did NOT test this version, because I use functional components. 
componentDidMount() {
    firebase.auth().onAuthStateChanged( (user) => {
        if (user) {
            // Obviously, you can add more statements here, 
            //       e.g. call an action creator if you use Redux. 

            // navigate the user away from the login screens: 
            props.navigation.navigate("PermissionsScreen");
        } 
        else 
        {
            // reset state if you need to 
            this.setState({
                user: null,
                messageText: '',
                codeInput: '',
                phoneNo: '',
                confirmResult: null,
            });
        }
    });
};  
Bilal Abdeen
  • 1,627
  • 1
  • 19
  • 41
  • what're the benefits exactly in **else statement** I couldn't get it? – Oliver D May 30 '20 at 16:24
  • To my understanding, if your app has a "logout" functionality, the else statement would be executed. I never tried it because my app does not have a logout option. – Bilal Abdeen May 30 '20 at 22:17
  • @BilalAbdeen do you know from which Android version this feature is supported? I need to know because in case the OS does not verify automatically, some code is needed for manual verification – Smurfy Jul 30 '20 at 07:21
  • @Smurfy If I were you, I would make sure my code support manual verification anyway. – Bilal Abdeen Jul 30 '20 at 22:04
  • Yes you are right, but I need a test device that does not do auto-verification to make sure the code works correctly, hence the question – Smurfy Jul 31 '20 at 03:24
  • @Smurfy for this I think send verification sms to some other number and then enter the sms received on that numbe.r – Saeed Ansari Aug 04 '20 at 16:55
  • Is there anyway to prevent this 'smart' background verification? – The1993 Dec 04 '20 at 08:32
  • @The1993 did you get the solution?? – vaibhav gadekar Jun 18 '21 at 16:59
  • 1
    @BilalAbdeen how can we logout the user after successful verification? Because in my case, once the otp is verified for one user, it is automatically authenticating every preceding user, irrespective of the number used – salvi shahzad Aug 24 '21 at 06:49
  • @salvishahzad It does not sound right that "it automatically authenticates every user irrespective of the number used," I suggest that you do more troubleshooting to find what exactly is happening. If you could not figure it out, I suggest that you submit a new question regarding your problem. – Bilal Abdeen Aug 24 '21 at 22:10
  • @BilalAbdeen I checked it, it seems the user session is maintained once it it authenticated and next time when another user requests for an OTP, the previous user is still logged in – salvi shahzad Sep 05 '21 at 19:49
  • @salvishahzad Are you talking about the same device? When a user logs in to a device, he/she would stay connected forever. This is the expected behaviour. In the mobile world, the assumption is that devices are "personal." Users expect to stay logged in until they explicitly log off. – Bilal Abdeen Sep 05 '21 at 23:07
1

You need to check for background authentication using:

conponentDidMount() {    
firebase.auth().onAuthStateChanged((user) => {
            if (user) {
                // alert(JSON.stringify(user))
                // Obviously, you can add more statements here, 
                //       e.g. call an action creator if you use Redux. 
                // navigate the user away from the login screens: 
            }
        });
}

Then you need to log out your user once you are done with it (I faced this issue of old user session already present while new user was coming to authenticate)

So, at the time of logging out use:

if (firebase.auth().currentUser)
            firebase.auth().currentUser.delete();

And you are all set!

salvi shahzad
  • 1,092
  • 12
  • 20