ZOFTINO.COM android and web dev tutorials

Firebase Phone Number Authentication Android

Users can be authenticated using a phone number and one time code which is sent in SMS message. You can use Firebase authentication in your app to implement phone number authentication and sign-in.

In this post, you can learn how to implement Firebase phone number authentication with an example.

Firebase Phone Number Authentication Important Points

  • With Firebase phone number authentication, phone number verification is done when app runs first time on the device.
  • Verification is done by sending SMS message containing verification code.
  • Since Google stores phone numbers to prevent spam and abuse, you need to take user consent in you app for the same to use firebase phone number authentication.
  • Firebase phone number authentication doesn’t work on emulator.
  • Firebase phone number authentication API uses Google play services for tasks such as auto SMS verification.
  • You need to add to Firebase console the SHA-1 hash of certificate used to sign your app, as Firebase phone number authentication API uses Google play services.
  • As phone number authentication is less secure, it is a good practice to offer phone number authentication along with other more secure authentication methods.

Firebase Phone Number Authentication Setup

To use Firebase and Firebase authentication in your project, you need to follow Firebase SDK and Firebase authentication setup steps.

After you follow the steps, you need to enable Firebase phone number authentication in Firebase console. To do that, click target project then go to authentication, click sign-in method tab, enable phone and save changes.

Then you need to add your app's SHA-1 hash to your project in Firebase console. To do that, go to Firebase console main page, click the project, expand menu, click settings and enter SHA certificate fingerprint.

To know how to create SHA-1 hash, you can check facebook-login tutorial’s generate key hashes section.

Firebase Phone Number Authentication Flow

firebase phone number authentication android
  • App displays a screen to let user enter phone number and a button to submit it.
  • Then the phone number is validated to prevent empty value.
  • Verify the phone number using PhoneAuthProvider object by calling verifyPhoneNumber method on it, passing the phone number and PhoneAuthProvider.OnVerificationStateChangedCallbacks object.
  • OnVerificationStateChangedCallbacks callback has three callback methods, onVerificationCompleted, onVerificationFailed and onCodeSent. Method onCodeSent gets called after SMS containing verification code has been sent. Methods onVerificationCompleted and onVerificationFailed get called depending on the status of verification.
  • Some devices automatically verify SMS message without any user action, in such cases, first onCodeSent method is called and then onVerificationCompleted method is called.
  • Once verification is done automatically, you can sign the user in using PhoneAuthCredential object received in onVerificationCompleted callback method.
  • Sign-in can be performed by calling signInWithCredential method on PhoneAuthProvider object passing PhoneAuthCredential object.
  • Some devices don’t support auto verification, in that case, screen containing a text field to let user enter code needs to be displayed after SMS containing verification code is sent.
  • You need to save verification id received in onCodeSent call back method as it is required along with the code, which is sent in SMS message and user submits for verification, to create PhoneAuthCredential object. In the example, I used Firestore to store this info. SQLite database can also be used to store the info the on device itself.
  • After user submits verification code, you need to create PhoneAuthCredential using verification id and code that user enters and then call signInWithCredential method on PhoneAuthProvider object passing PhoneAuthCredential object.
  • In the OnCompleteListener, you check the status of sign-in. If sing-in is successful show action items that authenticated user can access. If sing-in is unsuccessful, display message.
  • All these steps to authenticate a phone number are followed when app is used for the first time. When user tries to access authenticated functionality every time after that, you need to just call verifyPhoneNumber method on PhoneAuthProvider to sign the user in.
  • To sing out a user, you need to call signOut method on PhoneAuthProvider object.

Activity

 import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
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 com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class FirebasePhoneNumAuthActivity extends AppCompatActivity {

    private static String uniqueIdentifier = null;
    private static final String UNIQUE_ID = "UNIQUE_ID";
    private static final long ONE_HOUR_MILLI = 60*60*1000;

    private static final String TAG = "FirebasePhoneNumAuth";

    private PhoneAuthProvider.OnVerificationStateChangedCallbacks callbacks;
    private FirebaseAuth firebaseAuth;

    private String phoneNumber;
    private Button sendCodeButton;
    private Button verifyCodeButton;
    private Button signOutButton;

    private EditText phoneNum;
    private EditText verifyCodeET;

    private FirebaseFirestore firestoreDB;
    private  FirebaseUser firebaseUser;

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

        sendCodeButton = findViewById(R.id.send_code_b);
        verifyCodeButton = findViewById(R.id.verify_code_b);
        signOutButton = findViewById(R.id.auth_logout_b);

        phoneNum = findViewById(R.id.phone);
        verifyCodeET = findViewById(R.id.phone_auth_code);

        addOnClickListeners();

        firebaseAuth = FirebaseAuth.getInstance();
        firestoreDB = FirebaseFirestore.getInstance();

        createCallback();
        getInstallationIdentifier();
        getVerificationDataFromFirestoreAndVerify(null);
    }

    private void addOnClickListeners() {
        sendCodeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                verifyPhoneNumberInit();
            }
        });
        verifyCodeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                verifyPhoneNumberCode();
            }
        });
        signOutButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                signOut();
            }
        });
    }

    private void createCallback() {
        callbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
            @Override
            public void onVerificationCompleted(PhoneAuthCredential credential) {
                Log.d(TAG, "verification completed" + credential);
                signInWithPhoneAuthCredential(credential);
            }

            @Override
            public void onVerificationFailed(FirebaseException e) {
                Log.w(TAG, "verification failed", e);
                if (e instanceof FirebaseAuthInvalidCredentialsException) {
                    phoneNum.setError("Invalid phone number.");
                } else if (e instanceof FirebaseTooManyRequestsException) {
                    Toast.makeText(FirebasePhoneNumAuthActivity.this,
                            "Trying too many timeS",
                            Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onCodeSent(String verificationId,
                                   PhoneAuthProvider.ForceResendingToken token) {

                Log.d(TAG, "code sent " + verificationId);
                addVerificationDataToFirestore(phoneNumber, verificationId);
            }
        };
    }

    private boolean validatePhoneNumber(String phoneNumber) {
        if (TextUtils.isEmpty(phoneNumber)) {
            phoneNum.setError("Invalid phone number.");
            return false;
        }
        return true;
    }
    private void verifyPhoneNumberInit() {
        phoneNumber = phoneNum.getText().toString();
        if (!validatePhoneNumber(phoneNumber)) {
            return;
        }
        verifyPhoneNumber(phoneNumber);

    }
    private void verifyPhoneNumber(String phno){
        PhoneAuthProvider.getInstance().verifyPhoneNumber(phno, 70,
                TimeUnit.SECONDS, this, callbacks);
    }
    private void verifyPhoneNumberCode() {
        final String phone_code = verifyCodeET.getText().toString();
        getVerificationDataFromFirestoreAndVerify(phone_code);
    }
    private void signInWithPhoneAuthCredential(PhoneAuthCredential credential) {
        firebaseAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (task.isSuccessful()) {
                            Log.d(TAG, "code verified signIn successful");
                            firebaseUser = task.getResult().getUser();
                            showSingInButtons();
                        } else {
                            Log.w(TAG, "code verification failed", task.getException());
                            if (task.getException() instanceof
                                    FirebaseAuthInvalidCredentialsException) {
                                verifyCodeET.setError("Invalid code.");
                            }
                        }
                    }
                });
    }
    private void createCredentialSignIn(String verificationId, String verifyCode) {
        PhoneAuthCredential credential = PhoneAuthProvider.
                getCredential(verificationId, verifyCode);
        signInWithPhoneAuthCredential(credential);
    }
    private void signOut() {
        firebaseAuth.signOut();
        showSendCodeButton();
    }
    private void addVerificationDataToFirestore(String phone, String verificationId) {
        Map verifyMap = new HashMap();
        verifyMap.put("phone", phone);
        verifyMap.put("verificationId", verificationId);
        verifyMap.put("timestamp",System.currentTimeMillis());

        firestoreDB.collection("phoneAuth").document(uniqueIdentifier)
                .set(verifyMap)
                .addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
                    @Override
                    public void onSuccess(DocumentReference documentReference) {
                        Log.d(TAG, "phone auth info added to db ");
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Log.w(TAG, "Error adding phone auth info", e);
                    }
                });
    }
    private void getVerificationDataFromFirestoreAndVerify(final String code) {
        initButtons();
        firestoreDB.collection("phoneAuth").document(uniqueIdentifier)
                .get()
                .addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                        if (task.isSuccessful()) {
                            DocumentSnapshot ds = task.getResult();
                            if(ds.exists()){
                                disableSendCodeButton(ds.getLong("timestamp"));
                                if(code != null){
                                    createCredentialSignIn(ds.getString("verificationId"),
                                            code);
                                }else{
                                    verifyPhoneNumber(ds.getString("phone"));
                                }
                            }else{
                                showSendCodeButton();
                                Log.d(TAG, "Code hasn't been sent yet");
                            }

                        } else {
                            Log.d(TAG, "Error getting document: ", task.getException());
                        }
                    }
                });
    }
    public synchronized String getInstallationIdentifier() {
        if (uniqueIdentifier == null) {
            SharedPreferences sharedPrefs = this.getSharedPreferences(
                    UNIQUE_ID, Context.MODE_PRIVATE);
            uniqueIdentifier = sharedPrefs.getString(UNIQUE_ID, null);
            if (uniqueIdentifier == null) {
                uniqueIdentifier = UUID.randomUUID().toString();
                SharedPreferences.Editor editor = sharedPrefs.edit();
                editor.putString(UNIQUE_ID, uniqueIdentifier);
                editor.commit();
            }
        }
        return uniqueIdentifier;
    }
    private void disableSendCodeButton(long codeSentTimestamp){
        long timeElapsed = System.currentTimeMillis()- codeSentTimestamp;
        if(timeElapsed > ONE_HOUR_MILLI){
            showSendCodeButton();
        }else{
            findViewById(R.id.phone_auth_items).setVisibility(View.GONE);
            findViewById(R.id.phone_auth_code_items).setVisibility(View.VISIBLE);
            findViewById(R.id.logout_items).setVisibility(View.GONE);
        }
    }
    private void showSendCodeButton(){
        findViewById(R.id.phone_auth_items).setVisibility(View.VISIBLE);
        findViewById(R.id.phone_auth_code_items).setVisibility(View.GONE);
        findViewById(R.id.logout_items).setVisibility(View.GONE);
    }
    private void showSingInButtons(){
        findViewById(R.id.phone_auth_items).setVisibility(View.GONE);
        findViewById(R.id.phone_auth_code_items).setVisibility(View.GONE);
        findViewById(R.id.logout_items).setVisibility(View.VISIBLE);
    }
    private void initButtons(){
        findViewById(R.id.phone_auth_items).setVisibility(View.GONE);
        findViewById(R.id.phone_auth_code_items).setVisibility(View.GONE);
        findViewById(R.id.logout_items).setVisibility(View.GONE);
    }
}

Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".FirebasePhoneNumAuthActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary" />
    <android.support.constraint.ConstraintLayout
        android:id="@+id/phone_auth_items"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/phone_auth_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="16dp"
            android:text="Phone number sign-in"
            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <android.support.design.widget.TextInputLayout
            android:id="@+id/phone_l"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/phone_auth_tv">

            <EditText
                android:id="@+id/phone"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Phone number" />
        </android.support.design.widget.TextInputLayout>
        <Button
            android:id="@+id/send_code_b"
            style="@style/Widget.AppCompat.Button.Colored"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="16dp"
            android:text="Send Code"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/phone_l" />
    </android.support.constraint.ConstraintLayout>
    <android.support.constraint.ConstraintLayout
        android:id="@+id/phone_auth_code_items"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <android.support.design.widget.TextInputLayout
            android:id="@+id/phone_auth_code_l"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent">
            <EditText
                android:id="@+id/phone_auth_code"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Phone auth code" />
        </android.support.design.widget.TextInputLayout>
        <Button
            android:id="@+id/verify_code_b"
            style="@style/Widget.AppCompat.Button.Colored"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="16dp"
            android:text="Verify Code"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/phone_auth_code_l" />
    </android.support.constraint.ConstraintLayout>
    <android.support.constraint.ConstraintLayout
        android:id="@+id/logout_items"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/auth_logout_b"
            style="@style/Widget.AppCompat.Button.Colored"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="16dp"
            android:text="Logout"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </android.support.constraint.ConstraintLayout>
</LinearLayout>