Firebase authentication provides a way to add user account creation and sign in capabilities to an app with a minimal amount of coding. Once a user has been authenticated with Firebase, the user is assigned a unique Firebase user ID which can be used when integrating other Firebase services such as data storage and cloud messaging.
Firebase uses the concept of authentication providers to facilitate the identification and registration of users. The list of supported Firebase authentication providers currently consists of Google, Facebook, Twitter, GitHub, phone number and email/password authentication.
Two forms of Firebase authentication are available, one involving the use of FirebaseUI Auth and the other a lower level approach using the Firebase SDK.
In practice, these involve the use of the following collection of Firebase authentication classes.
getCurrentUser()
method of the FirebaseAuth
instance. The data stored in the object will vary depending on which authentication provider is currently being used, but typically includes information such as the user's display name, email address, a URL to a profile photo and the ID of the authentication provider used to sign into the app.AuthCredential
class is used to encapsulate user account credentials in a way that is compatible with Firebase. This class is used when exchanging a token from a third-party authentication provider for the credentials of a Firebase account. For each authentication provider there is a corresponding AuthCredential
subclass: EmailAuthCredential
, PhoneAuthCredential
, FacebookAuthCredential
, GithubAuthCredential
, GoogleAuthCredential
, TwitterAuthCredential
.In this tutorial I'm going to show how to sign in via Google Sign-In API and link existed Firebase account (Google) with new one (Facebook).
Firebase Authentication with the Google Sign-In API
The Google Play Services library includes a set of APIs that provide access to a range of Google services. One such API is the Google Sign-In API which, as the name suggests, allows app developers to provide users the ability to sign into Google accounts from with an Android app. Once a user has successfully signed into a Google account, the ID token for that Google account can then be used to register the user via the Firebase authentication system. In effect this connects the user’s Google account with a corresponding Firebase authentication account, allowing the user to continue signing into the app as a Firebase registered user using the Google account details.
The core elements of Google sign-in are the GoogleSignInOptions
and GoogleApiClient
classes. GoogleSignInOptions
is used to configured the way in which the Google sign-in operation is handled, and specifies the Google account information that is required by the Android app. GoogleApiClient
provides a convenient interface for working with Google Play Services APIs without having to write extensive code and error handling logic. A GoogleApiClient
instance is initialized with a suitably configured GoogleSignInOptions
instance and then used to launch the Google sign-in activity. This activity takes the user through the Google sign-in process and returns the result to the app.
If the user successfully signed into a Google account using the Google user sign-in activity, the resulting data returned to the app will include the user’s Google account ID token. The Firebase SDK is then used to exchange this ID for a Firebase credential object which is, in turn, used to register the user within the Firebase authentication system and subsequently sign into the app.
To sign in users with the Google Sign-In API, you must first enable the Google sign-in method for your Firebase project:
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:
The Google authentication provider makes use of the Google Play Services authentication library. Within the Android Studio project tool window, locate and open the build.gradle (app: module) build file (located under Gradle Scripts) and add the library to the dependencies section of the file:
compile 'com.google.firebase:firebase-auth:11.4.2' compile 'com.google.android.gms:play-services-auth:11.4.2'
The user interface layout is going to consist of two TextViews
and two Buttons
. You can see layout in below snippet
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal"> <TextView android:id="@+id/tvStatus" android:text="Status" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tvUser" android:text="User" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/btnSignIn" android:text="Sign In" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="signIn"/> <Button android:id="@+id/btnSignOut" android:text="Sign Out" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="signOut"/> </LinearLayout>
Result
Following is the MainActivity.java
.
public class MainActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener { private TextView tvStatus, tvUser; private Button btnSignIn, btnSignOut; private FirebaseAuth fbAuth; private FirebaseAuth.AuthStateListener authListener; private String TAG = MainActivity.class.getSimpleName(); private GoogleApiClient googleApiClient; private GoogleSignInOptions signInOptions; private int RC_SIGN_IN = 12345; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvStatus = findViewById(R.id.tvStatus); tvUser = findViewById(R.id.tvUser); btnSignIn = findViewById(R.id.btnSignIn); btnSignOut = findViewById(R.id.btnSignOut); tvStatus.setText("Signed out"); initFirebaseAuth(); initGoogleAuth(); } private void initGoogleAuth() { signInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(getString(R.string.default_web_client_id)) .requestEmail() .requestProfile() .build(); googleApiClient = new GoogleApiClient.Builder(this) .enableAutoManage(this, this) .addApi(Auth.GOOGLE_SIGN_IN_API, signInOptions) .build(); } private void initFirebaseAuth() { fbAuth = FirebaseAuth.getInstance(); authListener = new FirebaseAuth.AuthStateListener() { @Override public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { FirebaseUser user = firebaseAuth.getCurrentUser(); if (user != null) { tvUser.setText(user.getEmail()); tvStatus.setText("Signed In"); Log.d(TAG, "onAuthStateChanged: " + user.getPhotoUrl()); } else { tvUser.setText(""); tvStatus.setText("Signed out"); } } }; } @Override public void onStop() { super.onStop(); if (authListener != null) { fbAuth.removeAuthStateListener(authListener); } } @Override public void onStart() { super.onStart(); fbAuth.addAuthStateListener(authListener); } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { Log.d(TAG, "onConnectionFailed: Google Play Services failure"); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == RC_SIGN_IN) { GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); if (result.isSuccess()) { GoogleSignInAccount account = result.getSignInAccount(); authWithFirebase(account); } else { Log.d(TAG, "onActivityResult: Google sign-in failed."); } } } public void signIn(View view) { Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient); startActivityForResult(signInIntent, RC_SIGN_IN); } public void signOut(View view) { fbAuth.signOut(); Auth.GoogleSignInApi.signOut(googleApiClient).setResultCallback( new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) {} }); } private void authWithFirebase(GoogleSignInAccount acct) { AuthCredential credential = GoogleAuthProvider.getCredential( acct.getIdToken(), null); fbAuth.signInWithCredential(credential) .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (!task.isSuccessful()) { Log.d(TAG, "Firebase authentication failed."); } } }); } }
Linking and Unlinking Firebase Authentication Providers
Firebase account linking allows users to sign into the same account using different authentication providers. By linking the user’s Facebook and Twitter credentials, for example, the user can sign into the same account using either sign-in provider. Having linked these accounts, the user is then able to sign into the app using either of those accounts.
Consider, for the purposes of an example, a user registered to access an app using an account created via the Firebase Facebook authentication provider. If the user creates a new account using the Google authentication provider and links that new account to the original Facebook-based account, either account can then be used to sign into the app.
Limitations of Firebase Account Linking. When using account linking it is important to be aware that some limitations exist. First, only two accounts can participate in a link. If an attempt is made to link to an account which is already linked, the new link will replace the original link.
It is also not possible to link two accounts associated with the same authentication provider. While a Facebook account may be linked with a Google account, for example, it is not possible to link two Google provider based accounts. An attempt to link accounts from the same provider will result in an exception containing a message which reads as follows: User has already been linked to the given provider.
Account linking can only be performed at the point at which a new account is created. It is not possible, in other words, to link two pre-existing accounts. A workaround to this limitation is to delete one of the two accounts and then establish the link while re-creating the account.
All that is required is the AuthCredential
object for the new account and the FirebaseUser
instance for the account with which the link is to be established.
First of all, create Facebook login form as described in Authenticate Using Facebook Login.
public class MainActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... initFb(); } private void initFb() { callbackManager = CallbackManager.Factory.create(); tvLink = findViewById(R.id.tvLink); btnLogin = findViewById(R.id.btnLogin); btnLogin.setReadPermissions("public_profile"); btnLogin.registerCallback(callbackManager, new FacebookCallback<LoginResult>() { @Override public void onSuccess(LoginResult loginResult) { AccessToken token = loginResult.getAccessToken(); Log.d(TAG, "onSuccess: " + token.getUserId()); } @Override public void onCancel() { Log.d(TAG, "onCancel: Login attempt canceled."); } @Override public void onError(FacebookException e) { Log.d(TAG, "onError: Login attempt failed."); } }); } public void linkFb(View v) { AccessToken accessToken = AccessToken.getCurrentAccessToken(); AuthCredential credential = FacebookAuthProvider.getCredential(accessToken.getToken()); fbAuth.getCurrentUser().linkWithCredential(credential) .addOnCompleteListener(activity, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (!task.isSuccessful()) { Toast.makeText(activity, "Authentication failed.", Toast.LENGTH_SHORT).show(); Log.d(TAG, "onComplete: " + task.getException().getMessage()); } else { FirebaseUser user = task.getResult().getUser(); StringBuilder sb = new StringBuilder("Name: " + user.getDisplayName()); sb.append("\nProvider: " + user.getProviderId()); tvLink.setText(sb.toString()); } } }); } }
Result
Unlinking an Authentication Provider
An existing link can be removed from an account by calling the unlink()
method of the FirebaseUser
object for the current user, passing through the provider ID of the account to be unlinked.
A list of provider IDs associated with an account can be obtained by calling the getProviderData()
method of the current user’s FirebaseUser
object as follows:
FirebaseUser user = fbAuth.getCurrentUser(); List<? extends UserInfo> providerData = user.getProviderData();
The above method call returns a list of UserInfo
objects, each containing the provider for the corresponding account. The following code iterates through the UserInfo
objects in the above providerData
list and outputs the provider ID for each to the console:
for (UserInfo userInfo : providerData ) { String providerId = userInfo.getProviderId(); Log.d(TAG, "providerId = " + providerId); }
Unlinking code should be implemented such that it identifies a specific provider and then unlinks it from the current user account. The following code, for example, unlinks the user’s Google provider-based account:
for (UserInfo userInfo : providerData ) { String providerId = userInfo.getProviderId(); Log.d(TAG, "providerId = " + userInfo.getProviderId()); if (providerId.equals("google.com")) { user.unlink(providerId) .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (!task.isSuccessful()) { // Handle error } } }); } }
Once the Google authentication provider account has been unlinked, the user will no longer be able to sign into the app using those credentials leaving only the original account available for signing in.
Unlinking the only authentication provider registered for a user account will turn the account into an anonymous account. As such, the user will only have access to the account and associated Firebase stored data for the remainder of the current log in session and once the user logs out, that access will be lost. The user should either be prompted to link a different provider based account to the anonymous account before logging out or, more preferably, prevented from unlinking the last remaining authentication provider.