Facebook Authentication using the Firebase SDK
Previous | Table of Contents | Next |
Google Authentication using the Firebase SDK | Twitter Log In Authentication using the Firebase SDK |
The previous chapter covered Firebase SDK based authentication using the Google provider. While some concepts are the same, there are also a number of different requirements that must be fulfilled when implementing Firebase SDK authentication using Facebook to identify and authenticate app users.
This chapter will begin by outlining the general steps to achieve Facebook-based authentication by combining both the Firebase and Facebook SDKs. Once these basics have been covered, the chapter will work through the creation of an example project.
Authentication with the Firebase and Facebook SDKs
Implementing Firebase authentication using both the Firebase and Facebook SDKs is a multistep process which can be broken down into a sequence of steps involving a number of different classes.
As with the Google authentication process outlined in the previous chapter, this form of authentication requires that the user first sign into a valid Facebook account in order to receive an access token from the Facebook SDK. This access token is then used to create a Firebase authentication credential. The Firebase credential is then used to sign into a Firebase authentication user account (including the creation of a new account if one does not already exit).
The cornerstone to performing the Facebook phase of the authentication is the LoginButton class. This class provides a button for inclusion within a user interface which, when appropriately configured, serves the dual role of being both the login and logout button. Callbacks are then registered on the LoginButton instance which are called by the Facebook SDK to notify the app of the status of Facebook sign-in requests. On a successful Facebook sign-in, the appropriate callback method is called and passed a LoginResult object containing the user’s access token which is then used to register the user in Firebase.
Creating the FacebookAuth Project
The first step in this exercise is to create the new project. Begin by launching Android Studio and, if necessary, closing any currently open projects using the File -> Close Project menu option so that the Welcome screen appears.
Select the Start a new Android Studio project quick start option from the welcome screen and, within the new project dialog, enter FacebookAuth into the Application name field and your domain as the Company Domain setting before clicking on the Next button.
On the form factors screen, enable the Phone and Tablet option and set the minimum SDK to API 16: Android 4.1 (Jellybean). Continue to proceed through the screens, requesting the creation of an Empty Activity named FacebookAuthActivity with a corresponding layout named activity_facebook_auth.
Connecting the Project to Firebase
As with previous examples, the project will need to be connected to Firebase. Within Android Studio, use the Tools -> Firebase menu to display the Firebase Assistant panel, locate and click on the Authentication category, select the Email and password authentication link and then click on the Connect to Firebase button to display the Firebase connection dialog.
Choose the option to store the app in an existing Firebase project and select the Firebase Examples project created in the beginning of the book.
With the project’s Firebase connection established, refer to the Firebase assistant panel once again, this time clicking on the Add Firebase Authentication to your app button. A dialog will appear outlining the changes that will be made to the project build configuration to enable Firebase authentication. Click on the Accept Changes button to commit the changes to the project configuration.
Firebase and Facebook Console Configuration
Many of the steps for configuring the project to support Facebook authentication are the same as those outlined in the chapter entitled Facebook Login Authentication using FirebaseUI Auth. In fact, if you have already completed the steps outlined in that chapter, then the following steps will already have been performed and do not need to be repeated for this example:
1. Obtain the Facebook App ID and App Secret from the Facebook for Developers portal.
2. Enable the Facebook provider in the Firebase console.
3. Add the App ID and App Secret to the project in the Firebase console.
4. Add the Facebook login product to the app within the Facebook portal.
5. Obtain the OAuth Redirect URI from the Firebase console and adding it to the app settings in the Facebook portal.
6. Generate the debug key hash for the project and adding to app configuration in Facebook portal.
If these steps have yet to be performed within your development environment, refer to the initial sections in the Facebook Login Authentication using FirebaseUI Auth chapter and follow the instructions to complete this phase of the project configuration before proceeding.
Configuring the Android Studio Project
With the work complete in the Firebase console and Facebook for Developers portal, there are now some tasks that need to be accomplished before Facebook Login can be used within the example app. Note that, unlike the console settings above, these steps are specific to each Android Studio project and must be performed for this project regardless of whether they were performed for a previous project.
The first task is to add a string declaration within the strings.xml file containing the Facebook App ID. With the FacebookAuth project loaded into Android Studio, use the project tool window to open the app -> res -> values -> strings.xml file. With the file loaded into the editor, add a new string entity with the name facebook_application_id as follows where <your_fb_app_id> is replaced by the actual App ID assigned within the Facebook portal:
<resources> <string name="app_name">FacebookAuth</string> <string name="facebook_application_id" translatable="false"><your_fb_app_id></string> <string name="action_settings">Settings</string> </resources>
Next, two new elements need to be added to the project manifest file, one to grant internet access permission to the app, and the other to assign the facebook_application_id string created above to the ApplicationId property of the Facebook SDK. Within the project tool window, select the manifests -> AndroidManifest.xml file and edit it to add these additional elements:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ebookfrenzy.facebookauth"> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".FacebookAuthActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_application_id"/> </application> </manifest>
Adding the Facebook SDK Library
Facebook authentication when using the Firebase SDK makes extensive use of support provided by the Facebook SDK. The module level build.gradle file must, therefore, be modified to ensure that this library is included as part of the project build process.
Locate the module level build.gradle file in the project tool window (Gradle Scripts -> build.gradle (Module: app)) and load it into the code editor. Within the file, scroll down to the dependencies section and add the directive to include the Facebook SDK:
. . dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.google.firebase:firebase-auth:11.0.1' compile 'com.facebook.android:facebook-android-sdk:[4,5)' testCompile 'junit:junit:4.12' } . .
When the yellow warning bar appears indicating that a project sync may be necessary, click on the Sync Now link and allow the project to be rebuilt to reflect the new Gradle build settings.
Designing the User Interface Layout
Initially, the user interface is going to consist of an ImageView, two TextViews and a Button. Begin by loading the activity_facebook_auth.xml file into the layout editor tool and turning off Autoconnect mode.
Select and delete the default “Hello World” TextView object and drag and drop an ImageView component from the Palette onto the layout canvas. When the ImageView is released onto the layout, a resources dialog will appear providing a list of images available to be displayed on the ImageView object. Inclusion of the Facebook SDK into the project has provided a range of Facebook related images from which to choose. Scroll down the rows of images in the resources dialog until the Facebook blank square profile picture image comes into view (highlighted in Figure 14-1):
Figure 14-1
With the image selected, click on the OK button to apply the image to the ImageView and dismiss the resources dialog.
Remaining within the layout editor, add the TextView and Button widgets to the layout so that it resembles the layout shown in Figure 14-2. Shift-click on the TextView buttons so that both are selected and increase the textSize attribute in the Properties tool window to 18sp:
Figure 14-2
Select the ImageView and, using the Properties tool window, change the ID to profileImage. Also change the IDs of the two TextView objects and the Button to emailText, statusText and loginButton respectively.
With the view components added to the layout it is now time to add some constraints. Click in the upper right-hand corner of the layout canvas and drag the resulting rectangle until it encompasses all of the widgets. On releasing the mouse button, all four widgets should now be selected. Right-click on the ImageView object and, from the resulting menu, select the Center Horizontally menu option. Repeat this step once again, this time selecting the Center Vertically menu option.
At this stage, appropriate constraints should have been added such that the layout will be responsive to different screen sizes and device orientations:
Figure 14-3
Adding the Facebook LoginButton
The Button object added to the layout in the previous section was used as a placeholder and now needs to be replaced by an instance of the Facebook LoginButton. With the activity_facebook_auth_sdk.xml layout resource file still open in the layout editor, switch the editor to text mode, locate the Button widget in the XML and change the class type from Button to com.facebook.login.widget.LoginButton as follows:
<com.facebook.login.widget.LoginButton android:text="Button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/loginButton" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/statusText" />
Return the layout editor to design mode and note the button appearance has changed to a Facebook LoginButton button:
Figure 14-4
Facebook SDK Initialization Steps
Facebook authentication setup involves initialization of the Facebook SDK, the creation of a CallbackManager and the registration of callback methods with the LoginButton instance. The callback manager and callback methods act as the bridge between the app and the Facebook SDK, allowing the SDK to notify the app of events relating to the authentication process.
Begin by loading the FacebookAuthActivity.java file into the Android Studio code editor and adding some variables and import directives that will be used later in the code:
package com.ebookfrenzy.facebookauth; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.content.Intent; import android.util.Log; import android.widget.ImageView; import android.widget.TextView; import com.facebook.CallbackManager; import com.facebook.FacebookCallback; import com.facebook.FacebookException; import com.facebook.FacebookSdk; import com.facebook.login.LoginResult; import com.facebook.login.widget.LoginButton; public class FacebookAuthActivity extends AppCompatActivity { private CallbackManager callbackManager; private LoginButton loginButton; private TextView emailText; private TextView statusText; private ImageView imageView; private static final String TAG = "FacebookAuth"; . . }
Next, modify the onCreate() method to perform some initialization tasks. When making the changes, be sure to place the Facebook SDK initialization call before the call to the setContentView() method. Failure to do so will cause the app to crash when the activity is launched:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); callbackManager = CallbackManager.Factory.create(); setContentView(R.layout.activity_facebook_auth); emailText = (TextView) findViewById(R.id.emailText); statusText = (TextView) findViewById(R.id.statusText); imageView = (ImageView) findViewById(R.id.profileImage); loginButton = (LoginButton) findViewById(R.id.loginButton); loginButton.setReadPermissions("email", "public_profile"); }
The purpose of the last line of code added to the method above is to configure the LoginButton instance with the permissions that will be required by our app during authentication. For the purposes of this example access will be required to the email address and public profile information associated with the user’s Facebook account.
Handling Authentication Results and Callbacks
Regardless of the fact that the Facebook authentication process is handled entirely in the background by the Facebook SDK, the app still needs a way to be notified of whether or not the authentication was successful.
When the user clicks the LoginButton, the Facebook SDK essentially uses the Android Intents mechanism to launch an activity which presents the user with the Facebook login interface and carries out the authentication process. Intents (android.content.Intent) are the messaging system by which one activity is able to launch another activity.
When another activity is launched by an intent, it can be configured such that a call to the onActivityResult of the originating activity is made when the launched activity finishes. It is via this mechanism that the Facebook SDK returns data to the app containing the results of the authentication.
Implement the onActivityResult() method within the FacebookAuthActivity class as follows:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); callbackManager.onActivityResult(requestCode, resultCode, data); }
The key line of code within the above method is the call to the onActivityResult() method of the callbackManager instance created during the Facebook SDK initialization process earlier in this chapter. Before this will do anything useful, a callback handler needs to be registered on the LoginButton and assigned to the CallbackManager instance.
This handler needs to contain methods to be called in the event of successful, failed and cancelled Facebook authentication requests.
Code to register and implement this handler this should now be added to the onCreate() method so that it reads as follows:
@Override protected void onCreate(Bundle savedInstanceState) { . . . imageView = (ImageView) findViewById(R.id.profileImage); loginButton = (LoginButton) findViewById(R.id.loginButton); loginButton.setReadPermissions("email", "public_profile"); loginButton.registerCallback(callbackManager, new FacebookCallback<LoginResult>() { @Override public void onSuccess(LoginResult loginResult) { Log.d(TAG, "onSuccess: " + loginResult); } @Override public void onCancel() { Log.d(TAG, "onCancel: User cancelled sign-in"); } @Override public void onError(FacebookException error) { Log.d(TAG, "onError: " + error); } }); }
In the event of a successful authentication, the onSuccess() callback method will be called and passed an object of type LoginResult. This object will be used later in this chapter to extract the access token and use it to authenticate the user with Firebase. For now, however, the methods are implemented to simply output information to the Android Studio logcat console.
Testing Facebook Authentication
At this point the Facebook phase of the authentication process is fully implemented and may be tested by running the app. Once launched, the user interface should appear, including the Facebook LoginButton. The result of clicking the button will depend on whether or not the Facebook app is installed on the device.
If the app is launched on a device on which the Facebook app is installed and set up with the user’s Facebook credentials, a dialog similar to that shown in Figure 14-5 may appear allowing the user to immediately sign in without entering a username and password:
Figure 14-5
If the Facebook app is installed, but does not have any existing Facebook account credentials configured, the standard Facebook app sign-in screen will be displayed prompting for the user’s account information.
Alternatively, if the app is running on an emulator or a device on which the Facebook app is absent, a WebView instance (Figure 14-6) will be launched requesting the user’s account information:
Figure 14-6
With the app running, tap the Facebook LoginButton and sign in to a valid Facebook account. Once the sign-in is complete, check the logcat output in the Android Studio Android Monitor panel for either a success or failure message. If the login was successful, the LoginButton will switch to “Log out” mode shown in Figure 14-7:
Figure 14-7
Initializing Firebase
Performing the Facebook login is, of course, only part of the Firebase authentication process. So far the app has been able to obtain a valid Facebook access token for the user, but no attempt has been made to exchange that token for a Firebase credential and register the user within the Firebase ecosystem. Before doing this, however, the now familiar steps of initializing the Firebase SDK and adding the authentication state listener must be completed.
Edit the FacebookAuthActivity.java file and add some more import statements and variables as follows:
package com.ebookfrenzy.facebookauth; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.ImageView; import android.widget.TextView; import com.facebook.CallbackManager; import com.facebook.FacebookCallback; import com.facebook.FacebookException; import com.facebook.FacebookSdk; import com.facebook.login.LoginResult; import com.facebook.login.widget.LoginButton; import com.google.firebase.auth.FirebaseAuth; import android.support.annotation.NonNull; import com.google.firebase.auth.FirebaseUser; public class FacebookAuthActivity extends AppCompatActivity { private CallbackManager callbackManager; private FirebaseAuth fbAuth; private FirebaseAuth.AuthStateListener authListener; . . .
Remaining in the FacebookAuthActivity class file, add the following code to the bottom of the onCreate() method to obtain a reference to the FirebaseAuth instance and install the authentication state listener:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); . . . fbAuth = FirebaseAuth.getInstance(); authListener = new FirebaseAuth.AuthStateListener() { @Override public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { FirebaseUser user = firebaseAuth.getCurrentUser(); if (user != null) { emailText.setText(user.getEmail()); statusText.setText("Signed In"); if (user.getPhotoUrl() != null) { displayImage(user.getPhotoUrl()); } } else { emailText.setText(""); statusText.setText("Signed Out"); imageView.setImageResource( R.drawable.com_facebook_profile_picture_blank_square); } } }; } @Override public void onStart() { super.onStart(); fbAuth.addAuthStateListener(authListener); } @Override public void onStop() { super.onStop(); if (authListener != null) { fbAuth.removeAuthStateListener(authListener); } }
On detection of a change to the authentication state, the above listener code checks for the presence of a valid user account. Depending on whether a valid user is detected, the user interface is updated accordingly, including displaying the user’s Facebook profile photo using a method named displayImage() which also now needs to be added to the class. Begin by adding import statements for the packages that will be needed by the image handling code:
package com.ebookfrenzy.facebookauth; import android.content.Intent; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.ImageView; import android.widget.TextView; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import com.google.firebase.auth.FirebaseAuth; import com.facebook.CallbackManager; import com.facebook.FacebookCallback; import com.facebook.FacebookException; import com.facebook.FacebookSdk; import com.facebook.login.LoginResult; import com.facebook.login.widget.LoginButton; import com.google.firebase.auth.FirebaseUser; import java.io.InputStream; . .
Next, add the code to download the user’s profile photo and display it on the ImageView object in the user interface layout:
void displayImage(Uri imageUrl) { new DownloadImageTask((ImageView) findViewById(R.id.profileImage)) .execute(imageUrl.toString()); } private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { ImageView bmImage; public DownloadImageTask(ImageView bmImage) { this.bmImage = bmImage; } protected Bitmap doInBackground(String... urls) { String urldisplay = urls[0]; Bitmap bitmap = null; try { InputStream in = new java.net.URL(urldisplay).openStream(); bitmap = BitmapFactory.decodeStream(in); } catch (Exception e) { e.printStackTrace(); } return bitmap; } protected void onPostExecute(Bitmap result) { bmImage.setImageBitmap(result); } }
With the appropriate steps implemented to detect authentication state changes, the next step is to use the Facebook access token to obtain a matching Firebase authentication credential, and then use that credential to sign the user in to the app.
Registering the User in Firebase
When the user’s Facebook identity has been successfully verified, the onSuccess() callback method previously registered on the LoginButton instance is called and passed a LoginResult object. Contained within this object is the user’s Facebook access token which needs to be exchanged for a corresponding Firebase AuthCredential object. This credential object will then be used to sign the user in to the app using the Firebase authentication system. If this is the first time that the user has signed into the app using a Facebook account, this process will automatically create a new Firebase authentication account for that user.
Begin by modifying the onSuccess() callback method to call a new method named exchangeAccessToken() which takes as an argument a Facebook AccessToken object:
. . loginButton.registerCallback(callbackManager, new FacebookCallback<LoginResult>() { @Override public void onSuccess(LoginResult loginResult) { Log.d(TAG, "onSuccess: " + loginResult); exchangeAccessToken(loginResult.getAccessToken()); } . .
With the call to the method added to the callback, the method must now also be added to the class, beginning with the task of importing some additional packages:
. . import android.widget.Toast; import com.facebook.AccessToken; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.firebase.auth.AuthCredential; import com.google.firebase.auth.AuthResult; import com.google.firebase.auth.FacebookAuthProvider; . .
Next, add the exchangeAccessToken() method so that it reads as follows:
private void exchangeAccessToken(AccessToken token) { AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken()); fbAuth.signInWithCredential(credential) .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (!task.isSuccessful()) { Toast.makeText(FacebookAuthActivity.this, "Authentication failed.", Toast.LENGTH_SHORT).show(); } } }); }
The code begins by generating Firebase credentials for the user by calling the getCredential() method of the FacebookAuthProvider class, passing through the Facebook access token as the sole argument. These credentials are passed through to the signInWithCredential() method of the FirebaseAuth instance. A completion handler is then used to check whether the sign-in was successful and to report any failure that may have occurred.
A successful Firebase authentication sign-in will trigger a call to the authentication state listener and the user interface will be updated to reflect the change in status and to display the user’s email address and profile photo.
Testing the Project
Open the Firebase console in a browser window, select the Authentication option in the left-hand navigation panel following by the Users tab on the Authentication screen. If a user already exists for the Facebook account you intend to use for testing purposes, delete it from Firebase before proceeding.
Compile and run the app on a physical Android device or emulator and tap the Log in with Facebook button. Depending on the configuration of the device, take the appropriate steps to authenticate using a valid Facebook account. On signing in, the user interface should update and the LoginButton should change to read “Log out”.
Return to the Firebase console, refresh the Users list and verify that the account has been created using the Facebook provider.
Click on the Log out button within the app, at which point the Facebook SDK will display a dialog seeking confirmation that you wish to log out:
Figure 14-8
Tap the log out option and note that the Log out button has now reverted to a Sign in with Facebook button. There is, however, a problem in that the user interface has not been updated to reflect that the user has signed out. The photo ImageView and the two TextView objects still suggest that the same user is still signed in. This is because the app has not noticed that the user selected the sign out option. Clearly some more work is required before this project is complete.
Detecting the Facebook Log Out
It is important to keep in mind when performing authentication of this nature that there are actually two authentications taking place, one with Facebook and another with Firebase. Although the Facebook LoginButton has callbacks registered within the app to receive notification of the status of a login attempt, no such callback is available for detecting when the user logs out of Facebook using the LoginButton. After signing out of Facebook, therefore, the user is still signed in using Firebase.
To resolve this problem, a way is needed to detect when the user has signed out of Facebook and, having detected this change, also sign out of Firebase. This can easily be achieved by creating an access token tracker instance within the app. An access token tracker is an instance of the AccessTokenTracker class of the Facebook SDK and provides a way for an app to receive notification when the status of an access token changes, such as a user signing out of an account.
Add an access token tracker to the app by editing the FacebookAuthActivity.java file and modifying the code as follows:
package com.ebookfrenzy.facebookauth; . . import com.facebook.AccessTokenTracker; . . public class FacebookAuthActivity extends AppCompatActivity { . . private AccessTokenTracker accessTokenTracker; . . @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FacebookSdk.sdkInitialize(getApplicationContext()); callbackManager = CallbackManager.Factory.create(); accessTokenTracker = new AccessTokenTracker() { @Override protected void onCurrentAccessTokenChanged( AccessToken oldAccessToken, AccessToken currentAccessToken) { if (currentAccessToken == null) { fbAuth.signOut(); } } }; . . }
The new code creates an access token tracker instance which simply checks to find out if a valid token still exists. In the event that the current token is null, the code assumes that the user has logged out of Facebook and takes the necessary action to also sign out of the corresponding Firebase account. This, in turn, will trigger the authentication state listener which will update the user interface to reflect that the user has now signed out.
Test the app once more and confirm that logging out now works as expected.
Summary
The chapter has outlined the use of the Facebook SDK to allow users to create and sign into Firebase authentication accounts using Facebook accounts. This involves obtaining an access token from Facebook for the user, then exchanging that token for corresponding Firebase credentials. These credentials are then used to register the user within Firebase. Interaction between the app, the Facebook SDK and Firebase is handled through a series of callbacks and listeners together with an access token tracker.
Previous | Table of Contents | Next |
Google Authentication using the Firebase SDK | Twitter Log In Authentication using the Firebase SDK |