diff --git a/docs/auth/phone-auth.md b/docs/auth/phone-auth.md index fbe501ac15..058e1b5bb6 100644 --- a/docs/auth/phone-auth.md +++ b/docs/auth/phone-auth.md @@ -227,3 +227,39 @@ export default function PhoneVerification() { } } ``` + +# Disabling automatic OTP verification on Android devices + +Firebase Auth automagically verifies the received OTP code from the SMS and in some certain devices it might throw `The SMS code has expired` error. + +Disable auto verify by adding this on `index.js` or `App.tsx`: + +```jsx +(async function () { + try { + await firebase.auth().settings.setAutoOTPVerify(false); + } catch (error) { + console.error(error); + } +})(); +``` + +To manually handle OTP verification code: + +```jsx +// set code and phone from textinputs +const [phone, setPhone] = useState(''); +const [code, setCode] = useState(''); + +const handlePhoneLogin = async () => { + try { + const confirmResult = await auth().signInWithPhoneNumber(phone); + + await confirmResult.confirm(code).then(user => { + console.log(user); + }); + } catch (e) { + console.log(e); + } +}; +``` diff --git a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java index 06c73c7698..db1ce355b9 100644 --- a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java +++ b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java @@ -981,7 +981,11 @@ private void signInWithProvider(String appName, ReadableMap provider, final Prom */ @ReactMethod public void signInWithPhoneNumber( - String appName, final String phoneNumber, final boolean forceResend, final Promise promise) { + String appName, + final String phoneNumber, + final boolean forceResend, + final boolean autoOTPVerify, + final Promise promise) { Log.d(TAG, "signInWithPhoneNumber"); FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); final FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); @@ -1080,10 +1084,16 @@ public void onCodeAutoRetrievalTimeOut(String verificationId) { if (forceResend && mForceResendingToken != null) { PhoneAuthProvider.getInstance(firebaseAuth) .verifyPhoneNumber( - phoneNumber, 60, TimeUnit.SECONDS, activity, callbacks, mForceResendingToken); + phoneNumber, + autoOTPVerify ? 60 : 0, + TimeUnit.SECONDS, + activity, + callbacks, + mForceResendingToken); } else { PhoneAuthProvider.getInstance(firebaseAuth) - .verifyPhoneNumber(phoneNumber, 60, TimeUnit.SECONDS, activity, callbacks); + .verifyPhoneNumber( + phoneNumber, autoOTPVerify ? 60 : 0, TimeUnit.SECONDS, activity, callbacks); } } } @@ -1113,7 +1123,12 @@ public void getSession(final String appName, final Promise promise) { @ReactMethod public void verifyPhoneNumberWithMultiFactorInfo( - final String appName, final String hintUid, final String sessionKey, final Promise promise) { + final String appName, + final String hintUid, + final String sessionKey, + final boolean autoOTPVerify, + final Promise promise + ) { final MultiFactorResolver resolver = mCachedResolvers.get(sessionKey); if (resolver == null) { // See https://firebase.google.com/docs/reference/node/firebase.auth.multifactorresolver for @@ -1153,7 +1168,7 @@ public void verifyPhoneNumberWithMultiFactorInfo( PhoneAuthOptions.newBuilder(firebaseAuth) .setActivity(activity) .setMultiFactorHint((PhoneMultiFactorInfo) selectedHint) - .setTimeout(30L, TimeUnit.SECONDS) + .setTimeout(autoOTPVerify ? 30L : 0L, TimeUnit.SECONDS) .setMultiFactorSession(resolver.getSession()) .setCallbacks( new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { @@ -1185,7 +1200,9 @@ public void verifyPhoneNumberForMultiFactor( final String appName, final String phoneNumber, final String sessionKey, - final Promise promise) { + final boolean autoOTPVerify, + final Promise promise + ) { final MultiFactorSession multiFactorSession = mMultiFactorSessions.get(sessionKey); if (multiFactorSession == null) { rejectPromiseWithCodeAndMessage( @@ -1198,7 +1215,7 @@ public void verifyPhoneNumberForMultiFactor( PhoneAuthOptions.newBuilder(firebaseAuth) .setPhoneNumber(phoneNumber) .setActivity(getCurrentActivity()) - .setTimeout(30L, TimeUnit.SECONDS) + .setTimeout(autoOTPVerify ? 30L : 0L, TimeUnit.SECONDS) .setMultiFactorSession(multiFactorSession) .requireSmsValidation(true) .setCallbacks( diff --git a/packages/auth/lib/Settings.js b/packages/auth/lib/Settings.js index 4ed5e3c2fc..f0754831c6 100644 --- a/packages/auth/lib/Settings.js +++ b/packages/auth/lib/Settings.js @@ -22,12 +22,21 @@ export default class Settings { this._auth = auth; this._forceRecaptchaFlowForTesting = false; this._appVerificationDisabledForTesting = false; + this._autoOTPVerify = true; /* Android only */ } get forceRecaptchaFlowForTesting() { return this._forceRecaptchaFlowForTesting; } + get autoOTPVerify() { + return this._autoOTPVerify; + } + + setAutoOTPVerify(autoOTPVerify) { + this._autoOTPVerify = autoOTPVerify; + } + set forceRecaptchaFlowForTesting(forceRecaptchaFlow) { if (isAndroid) { this._forceRecaptchaFlowForTesting = forceRecaptchaFlow; diff --git a/packages/auth/lib/index.d.ts b/packages/auth/lib/index.d.ts index 3ba68f2e91..cded0e748b 100644 --- a/packages/auth/lib/index.d.ts +++ b/packages/auth/lib/index.d.ts @@ -1116,6 +1116,39 @@ export namespace FirebaseAuthTypes { * @param smsCode The pre-set SMS code. */ setAutoRetrievedSmsCodeForPhoneNumber(phoneNumber: string, smsCode: string): Promise; + + /** + * Flag to disable automatic retrieval of OTP codes for the given phone number. + * When receiving an OTP verification code Android automagically digests it and + * in some cases throws this error: `The SMS code has expired` + * + * @android + */ + autoOTPVerify: boolean; + + /** + * Sets whether the automatic OTP (One-Time Password) verification should be disabled on Android devices. + * + * This method allows you to control the behavior of OTP verification by disabling the automatic retrieval + * and verification of SMS codes. This can be useful in testing scenarios or when you want to handle OTP + * verification manually if your users encounter `The SMS code has expired` error on some devices. + * + * @example + * ```js + * (async function () { + * try { + * await firebase.auth().settings.setAutoOTPVerify(false); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * ``` + * + * @android + * @param enabled whether auto OTP verify should be disabled, defaults to false + */ + setAutoOTPVerify(enabled: boolean): Promise; } /** diff --git a/packages/auth/lib/index.js b/packages/auth/lib/index.js index 0cc488d6c8..ad59b58dbd 100644 --- a/packages/auth/lib/index.js +++ b/packages/auth/lib/index.js @@ -282,7 +282,7 @@ class FirebaseAuthModule extends FirebaseModule { signInWithPhoneNumber(phoneNumber, forceResend) { if (isAndroid) { return this.native - .signInWithPhoneNumber(phoneNumber, forceResend || false) + .signInWithPhoneNumber(phoneNumber, forceResend || false, this._settings.autoOTPVerify) .then(result => new ConfirmationResult(this, result.verificationId)); } @@ -305,12 +305,20 @@ class FirebaseAuthModule extends FirebaseModule { } verifyPhoneNumberWithMultiFactorInfo(multiFactorHint, session) { - return this.native.verifyPhoneNumberWithMultiFactorInfo(multiFactorHint.uid, session); + return this.native.verifyPhoneNumberWithMultiFactorInfo( + multiFactorHint.uid, + session, + this._settings.autoOTPVerify, + ); } verifyPhoneNumberForMultiFactor(phoneInfoOptions) { const { phoneNumber, session } = phoneInfoOptions; - return this.native.verifyPhoneNumberForMultiFactor(phoneNumber, session); + return this.native.verifyPhoneNumberForMultiFactor( + phoneNumber, + session, + this._settings.autoOTPVerify, + ); } resolveMultiFactorSignIn(session, verificationId, verificationCode) {