Firebase for Android: Phone Number Authentication Android 05.12.2017

Firebase for Android: Phone Number Authentication

Firebase Phone Number Authentication allows users to sign into an app by providing a phone number. Before the user can sign in, a one-time code is sent via SMS to the provided number which must be entered into the app to gain access. Firebase will validate the phone number, handle any normalization logic that needs to happen, if this is a new user, or a returning one, and go ahead and generate a one-time code associated with this request.

Firebase's phone number sign-in request quota is high enough that most apps won't be affected. However, if you need to sign in a very high volume of users with phone authentication, you might need to upgrade your pricing plan. See the pricing page.

To sign in users by SMS, you must first enable the Phone Number sign-in method for your Firebase project:

  1. In the Firebase console, open the Authentication section.
  2. On the Sign-in Method page, enable the Phone Number sign-in method.

Second, connect your app to Firebase. If you're using the latest version of Android Studio (version 2.2 or later), it's recommend using the Firebase Assistant to connect your app to Firebase. The Firebase Assistant can connect your existing project or create a new one for you and automatically install any necessary gradle dependencies.

To open the Firebase Assistant in Android Studio:

  1. Click Tools > Firebase to open the Assistant window.
  2. Click to expand one of the listed features (for example, Analytics), then click the provided tutorial link (for example, Log an Analytics event).
  3. Click the Connect to Firebase button to connect to Firebase and add the necessary code to your app.

There are two ways to do Firebase Phone Number Authentication

  • FirebaseUI Android library. The main advantage of using FirebaseUI is that it is secure and hence you don’t have to write complex code of encrypting the user credentials. And it also handles all the cases, right from maintaining user sessions to password recovery.

Phone Number Authentication via FirebaseUI library

Add the following dependency to your app level build.gradle file to use FirebaseUI.

compile 'com.google.android.gms:play-services:11.4.2'
compile 'com.firebaseui:firebase-ui:3.1.0'

Make sure to add the Fabric repository to your project level build.gradle file. To do so, add the highlighted line.

allprojects {
    repositories {
        // ...
        maven { url 'https://maven.fabric.io/public' }
    }
}

Create an activity named PhoneAuthenticationActivity and add the below code.

public class PhoneAuthenticationActivity extends AppCompatActivity {
    private static final int RC_SIGN_IN = 123;
    private Activity activity = PhoneAuthenticationActivity.this;
    private String TAG = "TAG";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_phone_authentication);

        FirebaseAuth auth = FirebaseAuth.getInstance();

        if (auth.getCurrentUser() != null) {
            startActivity(new Intent(activity, LoggedActivity.class));
            finish();
        } else {
            // not signed in
            startActivityForResult(
                    AuthUI.getInstance()
                            .createSignInIntentBuilder()
                            .setAvailableProviders(
                                    Arrays.asList(
                                            new AuthUI.IdpConfig.Builder(AuthUI.PHONE_VERIFICATION_PROVIDER).build()
                                            ))
                            .build(),
                    RC_SIGN_IN);
        }
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == RC_SIGN_IN) {
            IdpResponse response = IdpResponse.fromResultIntent(data);

            if (resultCode == ResultCodes.OK) {
                startActivity(new Intent(activity, LoggedActivity.class));
                finish();
                return;
            } else {
                if (response == null) {
                    // User pressed back button
                    Log.d(TAG, "Login canceled by User");
                    return;
                }

                if (response.getErrorCode() == ErrorCodes.NO_NETWORK) {
                    Log.d(TAG, "No Internet Connection");
                    return;
                }

                if (response.getErrorCode() == ErrorCodes.UNKNOWN_ERROR) {
                    Log.d(TAG, "Unknown Error");
                    return;
                }
            }
            Log.d(TAG, "Unknown sign in response");
        }
    }
}

Create an activity named LoggedActivity and add the below code.

public class LoggedActivity extends AppCompatActivity {
    private String TAG = "TAG";
    private Activity activity = LoggedActivity.this;
    Button btnSignOut;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_logged);

        FirebaseAuth auth = FirebaseAuth.getInstance();
        if (auth.getCurrentUser() != null) {
            Log.d(TAG, "ProviderId: " + auth.getCurrentUser().getProviderId());
            Log.d(TAG, "PhoneNumber: " + auth.getCurrentUser().getPhoneNumber());
        } else {
            Log.d(TAG, "onCreate: EMPTY");
        }

        btnSignOut = (Button) findViewById(R.id.btn2);

        btnSignOut.setOnClickListener(view -> {
            auth.signOut();
            startActivity(new Intent(activity, PhoneActivity.class));
            finish();
        });
    }
}

Now when you run the app, you can see the Phone Number Authentication in action.

android_firebase_phone_auth1.png

You can create your own custom style in the styles.xml file and edit the UI as per your need. The custom style you create should have FirebaseUI as the parent and the three standard AppCompat color must be defined. For the demo, I am creating a style having the purple theme as shown below.

<style name="PurpleTheme" parent="FirebaseUI">
    <!-- Required standard AppCompat colors -->
    <item name="colorPrimary">@color/material_purple_500</item>
    <item name="colorPrimaryDark">@color/material_purple_700</item>
    <item name="colorAccent">@color/material_yellow_a700</item>

    <item name="colorControlNormal">@color/material_purple_500</item>
    <item name="colorControlActivated">@color/material_majenta_a700</item>
    <item name="colorControlHighlight">@color/material_purple_a200</item>
    <item name="android:windowBackground">@color/material_purple_50</item>
</style>

Once the custom style is created, set it in the sign-in intent as shown below.

startActivityForResult(
    AuthUI.getInstance(this).createSignInIntentBuilder()
        // ...
        .setTheme(R.style.PurpleTheme)
        .build());

If you also want to change the text that appears during the login flow, here’s the list.

Phone Number Authentication via Firebase SDK

Having covered Phone number authentication using FirebaseUI Auth in first part, this part will explain how to achieve similar results using the Firebase SDK.

Phone authentication using the Firebase SDK involves asking the user for a phone number, sending a 6-digit verification code via SMS message, and then verifying that the code is valid. If the code entered is valid, the app can take the usual steps to sign the user in. In the event that the code is not received by the user, the app should typically provide the user the option to resend the code.

All of the work involved in generating the code and sending the SMS message is handled by a call to the verifyPhoneNumber() method of the PhoneAuthProvider instance. This will trigger a call to one of the following callback methods:

  • onVerificationCompleted(). Called only on certain device configurations (typically devices containing a SIM card) where the verification code can be verified automatically on the device without the user having to manually enter it.
  • onVerificationFailed(). Indicates that an error occurred when sending the activation code. This is usually the result of the user entering an invalid or incorrectly formatted phone number.
  • onCodeSent(). Called after the code has been sent. This method is passed a verification ID and a resend token that should be referenced when making a code resend request.
  • onCodeAutoRetrievalTimeOut(). When the verifyPhoneNumber() method is called it is passed a timeout value. This method is called if the verification process is not completed within the given timescale.

Once the verification code has been entered by the user, the code and the verification ID provided to the onCodeSent() callback are passed through to the getCredential() method of the PhoneAuthProvider class. This returns a PhoneAuthCredential object which can be used to sign the user into the app in the usual way via a call to the signInWithCredential() method of the FirebaseAuth class.

So, connect your app to Firebase as described above.

Phone verification makes use of the latest version of the firebase-auth library. Add the following dependency to your app level build.gradle file to use FirebaseUI.

compile 'com.google.firebase:firebase-auth:11.4.2'

The user interface for the main activity is going to consist of four Buttons, two EditText views and a TextView object.

android_firebase_phone_auth2.png

Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:paddingTop="30dp">

    <TextView
        android:id="@+id/tvStatus"
        android:text="Signed Out"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/edPhoneNumber"
        android:hint="Phone number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btnSendCode"
        android:onClick="sendPhoneNumber"
        android:text="Send code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btnResendCode"
        android:onClick="resendCode"
        android:text="Resend code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/edCode"
        android:hint="Code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btnVerifyCode"
        android:onClick="verifyCode"
        android:text="Verify code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btnSignOut"
        android:onClick="signOut"
        android:text="Sign out"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Create an activity named PhoneFbSdkActivity and add the below code.

public class PhoneFbSdkActivity extends AppCompatActivity {
    private String TAG = "PhoneFbSdkActivity";
    private int TIMEOUT = 60;
    private EditText edCode, edPhoneNumber;
    private Button btnSendCode, btnResendCode, btnVerifyCode, btnSignOut;
    private TextView tvStatus;
    private Activity activity = PhoneFbSdkActivity.this;

    private String phoneVerificationId;
    private PhoneAuthProvider.OnVerificationStateChangedCallbacks cbVerification;
    private PhoneAuthProvider.ForceResendingToken resendToken;
    private FirebaseAuth fbAuth;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_phone_fb_sdk);

        edPhoneNumber = (EditText) findViewById(R.id.edPhoneNumber);
        edCode = (EditText) findViewById(R.id.edCode);
        btnVerifyCode = (Button) findViewById(R.id.btnVerifyCode);
        btnSendCode = (Button) findViewById(R.id.btnSendCode);
        btnResendCode = (Button) findViewById(R.id.btnResendCode);
        btnSignOut = (Button) findViewById(R.id.btnSignOut);
        tvStatus = (TextView) findViewById(R.id.tvStatus);

        btnVerifyCode.setEnabled(false);
        btnResendCode.setEnabled(false);
        btnSignOut.setEnabled(false);
        tvStatus.setText("Signed Out");

        fbAuth = FirebaseAuth.getInstance();

        if (fbAuth.getCurrentUser() != null) {
            startActivity(new Intent(activity, LoggedActivity.class));
            finish();
        } else {
            Toast.makeText(activity, "Please login", Toast.LENGTH_SHORT).show();
        }        
    }

    public void sendPhoneNumber(View v) {
        String phoneNumber = edPhoneNumber.getText().toString();

        setUpVerificatonCallbacks();

        PhoneAuthProvider.getInstance().verifyPhoneNumber(
                phoneNumber,        // Phone number to verify
                TIMEOUT,                 // Timeout duration
                TimeUnit.SECONDS,   // Unit of timeout
                activity,               // Activity (for callback binding)
                cbVerification);
    }

    public void verifyCode(View view) {
        String code = edCode.getText().toString();

        PhoneAuthCredential credential = PhoneAuthProvider.getCredential(phoneVerificationId, code);
        signInWithPhoneAuthCredential(credential);
    }

    private void setUpVerificatonCallbacks() {
        cbVerification =
            new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {

                @Override
                public void onVerificationCompleted(PhoneAuthCredential credential) {
                    btnSignOut.setEnabled(true);
                    tvStatus.setText("Signed In");
                    btnResendCode.setEnabled(false);
                    btnVerifyCode.setEnabled(false);
                    edCode.setText("");
                    signInWithPhoneAuthCredential(credential);
                }

                @Override
                public void onVerificationFailed(FirebaseException e) {
                    if (e instanceof FirebaseAuthInvalidCredentialsException) {
                        // Invalid request
                        Log.d(TAG, "Invalid credential: " + e.getLocalizedMessage());
                    } else if (e instanceof FirebaseTooManyRequestsException) {
                        // SMS quota exceeded
                        Log.d(TAG, "SMS Quota exceeded.");
                    }
                }

                @Override
                public void onCodeSent(String verificationId, PhoneAuthProvider.ForceResendingToken token) {
                    phoneVerificationId = verificationId;
                    resendToken = token;

                    btnVerifyCode.setEnabled(true);
                    btnSendCode.setEnabled(false);
                    btnResendCode.setEnabled(true);
                }
            };
    }

    private void signInWithPhoneAuthCredential(PhoneAuthCredential credential) {
        fbAuth.signInWithCredential(credential)
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        btnSignOut.setEnabled(true);
                        edCode.setText("");
                        tvStatus.setText("Signed In");
                        btnResendCode.setEnabled(false);
                        btnVerifyCode.setEnabled(false);
                        FirebaseUser user = task.getResult().getUser();

                        Toast.makeText(activity, "Successfully logged", Toast.LENGTH_SHORT).show();

                        startActivity(new Intent(activity, LoggedActivity.class));
                        finish();
                    } else {
                        if (task.getException() instanceof
                                FirebaseAuthInvalidCredentialsException) {
                            Toast.makeText(activity, "The verification code entered was invalid", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            });
    }

    public void resendCode(View view) {
        String phoneNumber = edPhoneNumber.getText().toString();

        setUpVerificatonCallbacks();

        PhoneAuthProvider.getInstance().verifyPhoneNumber(
                phoneNumber,
                TIMEOUT,
                TimeUnit.SECONDS,
                activity,
                cbVerification,
                resendToken);
    }

    public void signOut(View view) {
        fbAuth.signOut();
        tvStatus.setText("Signed Out");
        btnSignOut.setEnabled(false);
        btnSendCode.setEnabled(true);
    }
}

The sendPhoneNumber() method calls the verifyPhoneNumber() method of the PhoneAuthProvider instance to send the code to the phone number entered into the edPhoneNumber EditText view. Before doing this, however, a call is made to set up the verification callbacks which are referenced in the final argument passed to the verifyPhoneNumber() method call. The next step, therefore, is to implement this method.

After the code arrives via SMS message and has been entered it into the code text field, the user is expected to click on the Verify Code button. This button as configured to call a method named verifyCode(). The method obtains the code from the EditText view and passes it, along with the verification ID saved within the onCodeSent() method to the getCredential() method of the PhoneAuthProvider instance. The credential returned by this method call is then passed to the signInWithPhoneCredential() method which now also needs to be implemented.

Regardless of whether the user manually verified the code, or if verification was automatic, the app has been provided with a PhoneAuthCredential object which can be used to sign into the app. The code for this showed in signInWithPhoneAuthCredential method.

resendCode() method is called when the Resend Code button is clicked and simply makes another call to the verifyPhoneNumber() method of the PhoneAuthProvider instance. This time, however, the method is also passed the resend token which was saved within the onCodeSent() callback method.