Firebase SMS Verification / Authentication

For a client project I'm creating a simple hybrid app that serves a very simple function, yet will have high traffic. The app wouldn't normally need a backend, since it's very very simple, and firebase seems like a perfect solution for the project.

The only part where I'm stuck at is SMS Verification / Authentication with Firebase. However, after some intense googling, and doc reading, I've come to realize that there's no easy way to do this. Here's what I've looked into so far :

  • Fabric.io Digits has a great JS API, however for some reason firebase and digits won't play nicely together : https://groups.google.com/forum/#!topic/firebase-talk/sB7lPuyCVBQ
  • Facebook Account Kit - Just a week ago, Facebook released a new kit for SMS Verification & Authentication, although it still feels like it has the same problem as fabric.io digits, at least until proven otherwise.
  • Twilio / Nexmo via NodeJS - These are both epic services with great JS APIs, however from what I understand this would require a separate backend server to handle JWT token exchange. And that on its own is another server, which would become the bottleneck during high traffic, and another point of vulnerability for security, the client team would have to manage separately. Not the most pleasant.
  • Twilio / Nexmo & Auth0 - So far this seems like the best option, where authentication & user management is handled by Auth0, however this solution can quickly get expensive given that both twilio or nexmo and auth0 are paid solutions. Not that I'm a cheapo expecting things to work for free - but feels like a very expensive extra step given that it is just to forward tokens. [see: clients-from-hell]
  • I remember reading somewhere, a suggestion like using phone numbers as emails on firebase like: 123-456-7890@example.com and use the security codes sent over sms as password, which sounds very sketchy for many different reasons.
  • Usually with hybrid mobile apps, the non-native nature of them or JS APIs are to blame, but for the first time (for me at least) it feels like this isn't the case. I presume at this point Firebase isn't a valid option, but wanted to ask the loving and caring members of the community one last time before starting to look into AWS, and setting up an entire backend for the client.

    Is there any other way to handle this type of authentication minus the middle-service / without a backend server? Anyone has any experience using these solutions?


    UPDATE : MAY 2017

    Phone Verification & Authentication is now natively available in Firebase. See my self-posted answer below.


    UPDATE : APR 2017

    Firebase now natively supports Cloud Functions. You can now accomplish this and a lot more using Cloud Functions without setting up any servers.


    As of May 17 2017 , the amazing people at Firebase have baked Digits' phone authentication into Firebase. This is now incredibly easy to achieve natively within Firebase, more or less with the flip of a switch and without the need of an external service or anything alike. You can read more about it in the docs :)


    I can't speak to every integration you mentioned, but you might want to try out another one Twilio's services, Authy.

    We've recently released production ready code samples via tutorials to help people work through these kinds of problems.

    One such example walks you through:

  • Sending a OneTouch push notification to mobile Authy app or
  • Sending a token through mobile Authy app or
  • Sending a one-time token in a text message sent with Authy via Twilio.
  • is the 2FA with Authy tutorial. The following Node.js snippet shows the endpoint waiting for user status to be approved or denied. If the User has approved the OneTouch request, we will save their session as confirmed , which officially logs them in.

    If the request was denied we render the /verify page and ask the User to log in with a Token.

    // Internal endpoint for checking the status of OneTouch
    exports.authyStatus = function(request, response) {
        var status = (request.user) ? request.user.authyStatus : 'unverified';
        if (status == 'approved') {
            request.session.confirmed = true;
            request.session.save(function(err) {
                if (err) return error(response, 500, 
                    'There was an error validating your session.');
            });
        }
        if (!request.session) {
            return error(response, 404, 'No valid session found for this user.');
        } else {
            response.send({ status: status });
        }   
    };
    

    So, this indeed requires you have a server. But given a go at the sample, this should help you decide what will work best for your app.


    import android.app.Activity;
    import android.os.Bundle;
    import android.support.annotation.NonNull;
    import android.text.TextUtils;
    import android.util.Log;
    import android.widget.EditText;
    import android.widget.Toast;
    
    import com.google.android.gms.tasks.OnCompleteListener;
    import com.google.android.gms.tasks.Task;
    import com.google.firebase.FirebaseException;
    import com.google.firebase.FirebaseTooManyRequestsException;
    import com.google.firebase.auth.AuthResult;
    import com.google.firebase.auth.FirebaseAuth;
    import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
    import com.google.firebase.auth.FirebaseUser;
    import com.google.firebase.auth.PhoneAuthCredential;
    import com.google.firebase.auth.PhoneAuthProvider;
    
    import java.util.concurrent.TimeUnit;
    
    public class PhoneAutenticationService {
    public PhoneAutenticationService(Activity activity,FirebaseAuth auth) {
        this.activity = activity;
        this.mAuth = auth;
        setupCallback();
    }
    
    private static final String TAG = PhoneAutenticationService.class.getSimpleName();
    
    private Activity activity;
    private String verificationCode;
    private static final String KEY_VERIFY_IN_PROGRESS = "key_verify_in_progress";
    
    private static final int STATE_INITIALIZED = 1;
    private static final int STATE_CODE_SENT = 2;
    private static final int STATE_VERIFY_FAILED = 3;
    private static final int STATE_VERIFY_SUCCESS = 4;
    private static final int STATE_SIGNIN_FAILED = 5;
    private static final int STATE_SIGNIN_SUCCESS = 6;
    
    // [START declare_auth]
    private FirebaseAuth mAuth;
    // [END declare_auth]
    
    private boolean mVerificationInProgress = false;
    private String mVerificationId;
    private PhoneAuthProvider.OnVerificationStateChangedCallbacks mCallbacks;
    private PhoneAuthProvider.ForceResendingToken mResendToken;
    
    protected void onSaveInstanceState(Bundle outState) {
        outState.putBoolean(KEY_VERIFY_IN_PROGRESS, mVerificationInProgress);
    }
    
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        mVerificationInProgress = savedInstanceState.getBoolean(KEY_VERIFY_IN_PROGRESS);
    }
    
    
    // [START on_start_check_user]
    public void onStart(EditText mPhoneNumberField) {
        // Check if user is signed in (non-null) and update UI accordingly.
        FirebaseUser currentUser = mAuth.getCurrentUser();
        updateUI(currentUser);
        // [START_EXCLUDE]
        if (mVerificationInProgress && validatePhoneNumber(mPhoneNumberField)) {
            startPhoneNumberVerification(mPhoneNumberField.getText().toString());
        }
        // [END_EXCLUDE]
    }
    // [END on_start_check_user]
    
    private void setupCallback(){
        mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
    
            @Override
            public void onVerificationCompleted(PhoneAuthCredential credential) {
                // This callback will be invoked in two situations:
                // 1 - Instant verification. In some cases the phone number can be instantly
                //     verified without needing to send or enter a verification code.
                // 2 - Auto-retrieval. On some devices Google Play services can automatically
                //     detect the incoming verification SMS and perform verificaiton without
                //     user action.
                Log.d(TAG, "onVerificationCompleted:" + credential);
                // [START_EXCLUDE silent]
                mVerificationInProgress = false;
                // [END_EXCLUDE]
    
                // [START_EXCLUDE silent]
                // Update the UI and attempt sign in with the phone credential
                updateUI(STATE_VERIFY_SUCCESS, credential);
                // [END_EXCLUDE]
                signInWithPhoneAuthCredential(credential);
            }
    
            @Override
            public void onVerificationFailed(FirebaseException e) {
                // This callback is invoked in an invalid request for verification is made,
                // for instance if the the phone number format is not valid.
                Log.w(TAG, "onVerificationFailed", e);
                // [START_EXCLUDE silent]
                mVerificationInProgress = false;
                // [END_EXCLUDE]
    
                if (e instanceof FirebaseAuthInvalidCredentialsException) {
                    // Invalid request
                    // [START_EXCLUDE]
                    Toast.makeText(activity,"Invalid phone number.",Toast.LENGTH_SHORT).show();
                    // [END_EXCLUDE]
                } else if (e instanceof FirebaseTooManyRequestsException) {
                    // The SMS quota for the project has been exceeded
                    // [START_EXCLUDE]
                    Toast.makeText(activity,"Quota exceeded.",Toast.LENGTH_SHORT).show();
    
                    // [END_EXCLUDE]
                }
    
                // Show a message and update the UI
                // [START_EXCLUDE]
                updateUI(STATE_VERIFY_FAILED);
                // [END_EXCLUDE]
            }
    
            @Override
            public void onCodeSent(String verificationId,
                                   PhoneAuthProvider.ForceResendingToken token) {
                // The SMS verification code has been sent to the provided phone number, we
                // now need to ask the user to enter the code and then construct a credential
                // by combining the code with a verification ID.
                Log.d(TAG, "onCodeSent:" + verificationId);
                Toast.makeText(activity,"onCodeSent:" + verificationId,Toast.LENGTH_SHORT).show();
                verificationCode = verificationId;
                // Save verification ID and resending token so we can use them later
                mVerificationId = verificationId;
                setVerificationCode(verificationId);
                mResendToken = token;
    
                // [START_EXCLUDE]
                // Update UI
                updateUI(STATE_CODE_SENT);
                // [END_EXCLUDE]
            }
    
        };
    }
    
    
    public void startPhoneNumberVerification(String phoneNumber) {
        // [START start_phone_auth]
        PhoneAuthProvider.getInstance().verifyPhoneNumber(
                phoneNumber,        // Phone number to verify
                60,                 // Timeout duration
                TimeUnit.SECONDS,   // Unit of timeout
                activity,               // Activity (for callback binding)
                mCallbacks);        // OnVerificationStateChangedCallbacks
        // [END start_phone_auth]
    
        mVerificationInProgress = true;
    }
    
    
    
    public void verifyPhoneNumberWithCode(String verificationId, String code) {
        // [START verify_with_code]
        PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, code);
    
        // [END verify_with_code]
        signInWithPhoneAuthCredential(credential);
    }
    
    // [START resend_verification]
    public void resendVerificationCode(String phoneNumber,
                                       PhoneAuthProvider.ForceResendingToken token) {
        PhoneAuthProvider.getInstance().verifyPhoneNumber(
                phoneNumber,        // Phone number to verify
                60,                 // Timeout duration
                TimeUnit.SECONDS,   // Unit of timeout
                activity,               // Activity (for callback binding)
                mCallbacks);        // resending
        // [END start_phone_auth]
    }
    // [END resend_verification]
    
    // [START sign_in_with_phone]
    public void signInWithPhoneAuthCredential(PhoneAuthCredential credential) {
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(activity, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (task.isSuccessful()) {
                            // Sign in success, update UI with the signed-in user's information
                            Log.d(TAG, "signInWithCredential:success");
                            Toast.makeText(activity,"signInWithCredential:success",Toast.LENGTH_SHORT).show();
                            FirebaseUser user = task.getResult().getUser();
                            // [START_EXCLUDE]
                            updateUI(STATE_SIGNIN_SUCCESS, user);
                            // [END_EXCLUDE]
                        } else {
                            // Sign in failed, display a message and update the UI
                            Log.w(TAG, "signInWithCredential:failure", task.getException());
                            if (task.getException() instanceof FirebaseAuthInvalidCredentialsException) {
                                // The verification code entered was invalid
                                // [START_EXCLUDE silent]
                                Toast.makeText(activity,"Invalid code.",Toast.LENGTH_SHORT).show();
                                // [END_EXCLUDE]
                            }
                            // [START_EXCLUDE silent]
                            // Update UI
                            updateUI(STATE_SIGNIN_FAILED);
                            // [END_EXCLUDE]
                        }
                    }
                });
    }
    // [END sign_in_with_phone]
    
    
    public void signOut() {
        mAuth.signOut();
        updateUI(STATE_INITIALIZED);
    }
    
    private void updateUI(int uiState) {
        updateUI(uiState, mAuth.getCurrentUser(), null);
    }
    
    public void updateUI(FirebaseUser user) {
        if (user != null) {
            updateUI(STATE_SIGNIN_SUCCESS, user);
        } else {
            updateUI(STATE_INITIALIZED);
        }
    }
    
    private void updateUI(int uiState, FirebaseUser user) {
        updateUI(uiState, user, null);
    }
    
    private void updateUI(int uiState, PhoneAuthCredential cred) {
        updateUI(uiState, null, cred);
    }
    
    private void updateUI(int uiState, FirebaseUser user, PhoneAuthCredential cred) {
        switch (uiState) {
            case STATE_INITIALIZED:
                // Initialized state, show only the phone number field and start button
                Toast.makeText(activity,"Initialized state",Toast.LENGTH_SHORT).show();
                break;
            case STATE_CODE_SENT:
                // Code sent state, show the verification field, the
                Toast.makeText(activity,"Code sent state",Toast.LENGTH_SHORT).show();
    
                break;
            case STATE_VERIFY_FAILED:
                // Verification has failed, show all options
                Toast.makeText(activity,"Verification has failed",Toast.LENGTH_SHORT).show();
    
                break;
            case STATE_VERIFY_SUCCESS:
                // Verification has succeeded, proceed to firebase sign in
                Toast.makeText(activity,"Verification has succeeded",Toast.LENGTH_SHORT).show();
    
                // Set the verification text based on the credential
                if (cred != null) {
                    if (cred.getSmsCode() != null) {
                        //mVerificationField.setText(cred.getSmsCode());
                    } else {
                        Toast.makeText(activity,"Invalid verification code.",Toast.LENGTH_SHORT).show();
                    }
                }
    
                break;
            case STATE_SIGNIN_FAILED:
                // No-op, handled by sign-in check
                Toast.makeText(activity,"Sign in failed",Toast.LENGTH_SHORT).show();
    
                break;
            case STATE_SIGNIN_SUCCESS:
                // Np-op, handled by sign-in check
                Toast.makeText(activity,"Sign in sucesssss!!!!",Toast.LENGTH_SHORT).show();
                break;
        }
    
        if (user == null) {
            // Signed out
    
        } else {
            // Signed in
        }
    }
    
    
    public boolean validatePhoneNumber(EditText mPhoneNumberField) {
        String phoneNumber = mPhoneNumberField.getText().toString();
        if (TextUtils.isEmpty(phoneNumber) || phoneNumber.length()>10 || phoneNumber.length()<9) {
            Toast.makeText(activity,"Invalid phone number.",Toast.LENGTH_SHORT).show();
            return false;
        }
    
        return true;
    }
    
    public PhoneAuthProvider.OnVerificationStateChangedCallbacks getmCallbacks() {
        return mCallbacks;
    }
    
    public PhoneAuthProvider.ForceResendingToken getmResendToken() {
        return mResendToken;
    }
    
    public FirebaseAuth getmAuth() {
        return mAuth;
    }
    
    public String getVerificationCode() {
        return verificationCode;
    }
    
    public void setVerificationCode(String verificationCode) {
        this.verificationCode = verificationCode;
    }
    

    }

    In your activity initialize Firebase auth and listener

     mAuth = FirebaseAuth.getInstance();
        mAuthListener = new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null) {
                    Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
                } else {
                    Log.d(TAG, "onAuthStateChanged:signed_out");
                }
                // ...
            }
        };
    
    
        //init all auth process
        phoneAutenticationService = new PhoneAutenticationService(this,mAuth);
    
     @Override
    public void onStart() {
        super.onStart();
        mAuth.addAuthStateListener(mAuthListener);
        getActivity().registerReceiver(smsBroadcastReceiver, filter);// define e broadcast receiver to intercept a sms verification code
    }
    
    @Override
    public void onStop() {
        super.onStop();
        if (mAuthListener != null) {
            mAuth.removeAuthStateListener(mAuthListener);sms code
        }
        getActivity().unregisterReceiver(smsBroadcastReceiver);
    
    }
    

    and finally call firebase method for authentication

    public void startAuthenticationByPhone(){
        if (!validatePhoneNumber(phoneInput)) {
            return;
        }
        startPhoneNumberVerification(phoneInput.getText().toString());
    
    }......
    
    链接地址: http://www.djcxy.com/p/33088.html

    上一篇: 无法在nexmo中显示此响应错误的概述

    下一篇: Firebase SMS验证/身份验证