capawesome-team / capacitor-firebase

⚡️ Firebase plugins for Capacitor. Supports Android, iOS and the Web.
https://capawesome.io/plugins/firebase/
Apache License 2.0
404 stars 102 forks source link

feat: add verifyPhoneNumber #38

Open kyleabens opened 2 years ago

kyleabens commented 2 years ago

Is your feature request related to a problem? Please describe: I use two-factor authentication in my app and would love it if you could implement verifyPhoneNumber. I'm not sure if signInWithPhoneNumber would work in this case (I haven't tried it yet);

Describe the solution you'd like:

To be able to generate a verificationId with verifyPhoneNumber.

Describe alternatives you've considered:

I've tried using the firebase sdk and it works on web and Android but iOS won't load the recaptcha needed to verify the phone number and generate the verificationId.

robingenz commented 2 years ago

Hi @kyleabens, this should already work (see https://github.com/robingenz/capacitor-firebase/tree/main/packages/authentication#signinwithphonenumber).

import { FirebaseAuthentication } from '@capacitor-firebase/authentication';

const signInWithPhoneNumber = async () => {
  const { verificationId } = await FirebaseAuthentication.signInWithPhoneNumber(
    {
      phoneNumber: '123456789',
    },
  );
  const verificationCode = window.prompt(
    'Please enter the verification code that was sent to your mobile device.',
  );
  await FirebaseAuthentication.signInWithPhoneNumber({
    verificationId,
    verificationCode,
  });
};

Let me know if this is what you are looking for.

kyleabens commented 2 years ago

@robingenz verifyPhoneNumber for multi factor authentication is a little different since it takes a session instead of a phone number. Would love to see this added as an option. Should still return a verification ID. I’ve been trying to piece it together by modifying the plugin but I’m struggling with swift and type casting. I keep getting an error when I try to use cap.getObject(“session”) because it’s then not the right type when passing to verifyPhoneNumber as MultiFactorSession type. I’m new to a lot of this so any advice would be great.

This is the exact error I get in xcode when I try to pass the session that I obtain in my ionic app

Cannot convert value of type 'JSObject?' (aka 'Optional<Dictionary<String, JSValue>>') to expected argument type 'MultiFactorSession'

robingenz commented 2 years ago

Okay, i will have a look 👍

trancee commented 2 years ago

Just as a side note, this would probably come in handy using the Multi-factor Auth: https://firebase.google.com/docs/auth/android/multi-factor

kyleabens commented 2 years ago

any update on adding this?

VictorienTardif commented 1 year ago

Hi, did you find a solution @kyleabens ? I also really need this.

etischenko commented 1 year ago

Also interested in this feature. I'm going to try creating a custom capacitor plugin just for the MFA feature on iOS but I would really love to just use this plugin instead.

Any updates?

typefox09 commented 1 year ago

Looking to do the same, any updates?

VictorienTardif commented 1 year ago

I did a quick and dirty patch to enable MFA on iOS: https://github.com/capawesome-team/capacitor-firebase/commit/2fe9ba0c24db2131c46335c95d7e23d94abb3709

This is really ugly, it was just the minimum and the fastest way to get something working.

  1. I added a verifyPhoneNumberToEnrollSecondFactor function that sends a SMS and return a verification ID.
  2. This is the really ugly part, on signInWithEmailAndPassword (but it should be on every sign in method), I intercept the auth/multi-factor-auth-required error, I send an SMS and... brrr... I reject the call with the verification ID as error message.

Not ideal at all but at least I was able to send the SMS to enroll second factors and on sign in when MFA is enabled.

typefox09 commented 1 year ago

I've noticed the problem with this it will only sign the user in on the native layer with MFA, however if we want to also sign them in on the JS layer (essential to use Firestore), it's not possible?

So for example, we sign the user in using your patch (on the native layer), we then take the credential returned and use this on the JS layer with signInWithCredential(auth, credential), would this 2nd call to login on the JS layer now fail with the error "auth/multi-factor-auth-required"?

VictorienTardif commented 1 year ago

I login the user both on the native layer and the JS layer in parallel and I use the verificationId from the native layer in the JS layer (and luckily it works).

This is what I do in JS:

// Sign in
  async signInWithEmail(email: string, password: string) {
    let verificationId: string | undefined;

    if (this.isIOS) {
      // To be able to handle second factor enrollment and verification on iOS
      // we have to be signed in on native layer
      try {
        await CapacitorFirebaseAuth.signInWithEmailAndPassword({
          email,
          password,
        });
      } catch (e) {
        // If a second factor is already enrolled,
        // an SMS is automatically send and the verificationId is returned as errorMessage
        if (
          e.code === 'auth/multi-factor-auth-required'
        ) {
          verificationId = e.errorMessage;
        }
      }
    }

    try {
      const result = await signInWithEmailAndPassword(
        this.auth,
        email,
        password
      );
      return Promise.resolve(result);
    } catch (e) {
      // On iOS, if a second factor auth is required,
      if (
        this.isIOS &&
        e instanceof FirebaseError &&
        e.code === 'auth/multi-factor-auth-required'
      ) {
      // we get the resolver from the JS error
        const resolver = getMultiFactorResolver(
          this.auth,
          e as MultiFactorError
        );
      }

        // We ask the user for the verificationCode
       // and we can use the verificationID we got from the native signin error

      const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
      const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);

      // resolve mfa signin 
      // Attention, the resolver is never resolved on the native layer. We only sign in the user on the JS layer
      resolver.resolveSignIn(multiFactorAssertion);

      //....
    }
  }
typefox09 commented 1 year ago

How will this work when we need to enroll users in MFA? There's a different process that happens there (still relies on Recaptcha)

VictorienTardif commented 1 year ago

That's why I added the verifyPhoneNumberToEnrollSecondFactor :)

In JS:

verifyPhoneNumberToEnrollSecondFactor(
    phoneNumber: string,
    recaptchaVerifier: RecaptchaVerifier
  ) {
    if (this.isIOS) {
      return CapacitorFirebaseAuth.verifyPhoneNumberToEnrollSecondFactor({
        phoneNumber,
      }).then(({ verificationId }) => verificationId);
    }

    return multiFactor(this.auth.currentUser)
      .getSession()
      .then((session) => {
        const phoneAuthProvider = new PhoneAuthProvider(this.auth);
        return phoneAuthProvider
          .verifyPhoneNumber({ phoneNumber, session }, recaptchaVerifier)
          .then((verificationId) => verificationId);
      });
  }

This way the SMS is sent on the native layer without using the racpatcha.

olanb7 commented 9 months ago

Is this still the best approach for supporting phone number MFA on iOS @robingenz?

VictorienTardif commented 9 months ago

"best" can't be used here, let's say the "less bad" lol. This is really not a good and reliable solution, besides I have a lot of problems with reCaptcha verification on iOS when silent notifications don't work (https://firebase.google.com/docs/auth/ios/phone-auth#set-up-recaptcha-verification), I don't understand how to deal with it.

VictorienTardif commented 9 months ago

Well, I finally added

if url.absoluteString.contains("://firebaseauth/link?deep_link_id=") {
  return true
}

in

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {

of AppDelegate.swift to prevent app to open the firebase dynamic link when it fallbacks to reCaptcha and it works :)

Again, a quick and dirty way to make things work.

kumarmanishc commented 6 months ago

With same package and feature I'm getting this error,

Module not found: Error: Can't resolve 'firebase/auth' in '/ionic-app/node_modules/@capacitor-firebase/authentication/dist/esm

kumarmanishc commented 6 months ago

Hey @kyleabens @robingenz I have fixed above issue and now sms is also getting sent without using reCaptcha.

However when we try to send OTP It's opening chrome and verifying recaptcha automatically.. My experience while using cordova was different.. App was verifying the request via sha256 and sending OTP without opening browser for recaptcha verification.

How can I avoid that?

`

await FirebaseAuthentication.addListener('phoneCodeSent', (event: any) => {
   this.verificationId = event.verificationId;
});

  await FirebaseAuthentication.signInWithPhoneNumber({
    phoneNumber: `${this.loginForm.value.phoneNumber}`,
  });

`

kumarmanishc commented 5 months ago

@robingenz

Any update on above ?

Google has changed their ways of verifying request using App Check.

But We don't have option to verify app-check with this sdk... I think that's the reason

robingenz commented 5 months ago

No updates yet. I'm currently working on a few other issues. Feel free to submit a PR. I will take a look then.