Blog Infos
Author
Published
Topics
, , , ,
Published
Photo by Daniel Romero on Unsplash
Takeaway from this article

In this article, you’ll learn how to authenticate users via Google Sign-in using the latest credential manager API. By the end of the article, you’ll be able to authenticate the user and extract basic profile details like display name, email, etc.

Introduction

In this modern era of mobile usage, every application needs to authenticate the user for some purpose. When it comes to authenticating users, mobile developers are not aware of several things.

That’s when the mighty social logins come in handy. Social login is a win-win for both users and mobile developers. Developers don’t need to go through the whole authentication mechanism, whereas users can sign in with less friction.

There are two reasons why we focus on Google Sign out of all the social logins we have in this modern world:

  1. There can be Android users who don’t have a Twitter or Facebook account but not a Google account.
  2. Second, the integration support, google is one of the innovative companies to actively work on making integrations as simple as possible.
A Brief History of Google-Sign-In in Android

Previously in Android Google Sign-In was implemented via GoogleSignInClient which is part of play-services-auth libraryYou can read all about GoogleSignInClient in this article.

A new library was introduced as part of the Jetpack suite called Credential Manager last year. It’s part of the androidX family: androidx.credentials. In favor of this, GoogleSignInClient was deprecated. So in modern Android development, the Credential Manager is the official approach to implementing Google Sign-In.

Enough with the theory, let’s get our hands dirty with the implementation.

Set Up Google API Console

Open your project in the Google API Console or create one if it doesn’t exist. Once the project is created navigate to the OAuth consent screen in the left navigation menu.

In this screen, you’ll be asked for information related to the application like App name, logo, privacy policy URL, etc. Be careful with what information you provide because it’ll be shown to the users trying to sign in to the application.

Once you’re done with the OAuth consent screen, navigate to the Credentials screen from the left navigation menu. Here we need to create two OAuth client IDs of Android and Web type.

To create an Android OAuth client ID, you must provide the application package name and the SHA-1 key. It’s important to note that SHA-1 keys are specific to each build type. If you provide the SHA-1 key for the debug variant, the OAuth client ID will only work with debug builds. Therefore, make sure to generate Android client IDs that align with your build requirements.

Next, create a web application client ID. You can skip the “Authorized JavaScript Origins” and “Authorized redirect URIs” fields for now. This client ID will be used to identify your backend server when it communicates with Google’s authentication services.

Copy the web application client ID into your project as we need it to authenticate the user.

Integration

Now that we’re done with project configuration, the next step is integration. In the app-level gradle file add the following libraries under the dependencies node:

// Google SignIn
implementation "androidx.credentials:credentials:1.2.2"
implementation "androidx.credentials:credentials-play-services-auth:1.2.2"
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.0"
Google Sign-In Request

Credential Manager provides two approaches to sign-in the user:

  1. GetGoogleIdOption : This is used when you want to have customer UI for the entry point of the Sign-In flow.
  2. Google button flow: Well the name itself describes the purpose.

Let’s start by creating a Google sign-in request using GetGoogleIdOption and CredentialManager where we use the web client that was created in the API console. We’ll create a singleton class with the name GoogleSignInManager to keep things simple. Have a look:

object GoogleSignInManager {

    private lateinit var credentialManager: CredentialManager

    suspend fun googleSignIn(
        context: Context,
        clientId: String,
    ) {

        if (::credentialManager.isInitialized.not()) {
            credentialManager = CredentialManager
                .create(context)
        }

        val googleIdOption: GetGoogleIdOption = GetGoogleIdOption
            .Builder()
            .setFilterByAuthorizedAccounts(true)
            .setServerClientId(clientId)
            .setAutoSelectEnabled(false)
            .build()

        val request: GetCredentialRequest = GetCredentialRequest
            .Builder()
            .addCredentialOption(googleIdOption)
            .build()
    }
}

First, check if the user has previously signed in by calling the API with setFilterByAuthorizedAccounts set to true. If authorized accounts are found, the user can choose one to sign in.

private suspend fun requestSignIn(
        context: Context,
        request: GetCredentialRequest,
) {
      try {
        val result = credentialManager.getCredential(
            request = request,
             context = context,
        )
    } catch (e: Exception){
        e.printStackTrace()
    }
}

If no authorized accounts are available, the request will throw NoCredentialException, then we need to prompt the user to sign in with any of their available accounts by calling the API again setFilterByAuthorizedAccounts set to false. That means we need to add a boolean parameter to googleSignIn the function indicating the status of setFilterByAuthorizedAccounts.

Along with filterByAuthorizedAccounts the parameter, we’ll also add two more lambdas doOnSuccess and doOnError which are supposed to be actions performed under respective circumstances. Have a look:

object GoogleSignInManager {

    private lateinit var credentialManager: CredentialManager

    suspend fun googleSignIn(
        context: Context,
        clientId: String,
        filterByAuthorizedAccounts: Boolean,
        doOnSuccess: (String) -> Unit,
        doOnError: (Exception) -> Unit,
    ) {
        if (::credentialManager.isInitialized.not()) {
            credentialManager = CredentialManager
                .create(context)
        }

        val googleIdOption: GetGoogleIdOption = GetGoogleIdOption
            .Builder()
            .setFilterByAuthorizedAccounts(true)
            .setServerClientId(clientId)
            .setAutoSelectEnabled(false)
            .build()

        val request: GetCredentialRequest = GetCredentialRequest
            .Builder()
            .addCredentialOption(googleIdOption)
            .build()

        requestSignIn(
            context, 
            request, 
            clientId, 
            filterByAuthorizedAccounts, 
            doOnSuccess, 
            doOnError
        )
    }

    private suspend fun requestSignIn(
        context: Context,
        request: GetCredentialRequest,
        clientId: String,
        filterByAuthorizedAccounts: Boolean,
        doOnSuccess: (String) -> Unit,
        doOnError: (Exception) -> Unit,
    ) {
        try {
            val result = credentialManager.getCredential(
                request = request,
                context = context,
            )
            doOnSuccess("")
        } catch (e: Exception){
            if (e is NoCredentialException && filterByAuthorizedAccounts) {
                googleSignIn(
                    context, 
                    clientId, 
                    false,
                    doOnSuccess,
                    doOnError
                )
            } else {
                doOnError(e)
            }
        }
    }
}

Let’s go through the code one last time, googleSignIn is the function that’s responsible for creating necessary objects with the input states, then it’ll trigger requestSignIn function which is responsible for initiating the request via CredentialManager and triggering doOnSuccess or doOnError based on the result.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

Extract Data From Credential Response

As we’ve authenticated the use via GetGoogleIdOption we need to make sure the credential response we receive is of type TYPE_GOOGLE_ID_TOKEN_CREDENTIAL. Then we will create a GoogleIdTokenCredential object from the response.

GoogleIdTokenCredential is the class from Google Identity Library which contains data like email ID, display name, phone number, profile picture, etc related to the authenticated account. Have a look at the implementation:

private fun handleCredentials(credential: Credential): String? {
    when (credential) {

        // GoogleIdToken credential
        is CustomCredential -> {
            if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
                try {
                    // Use googleIdTokenCredential and extract id to validate and
                    // authenticate on your server.
                    val googleIdTokenCredential = GoogleIdTokenCredential
                        .createFrom(credential.data)
                    return googleIdTokenCredential.displayName
                } catch (e: GoogleIdTokenParsingException) {
                    println("Received an invalid google id token response $e")
                }
            } else {
                // Catch any unrecognized custom credential type here.
                println("Unexpected type of credential")
            }
        }

        else -> {
            // Catch any unrecognized credential type here.
            println("Unexpected type of credential")
        }
    }
    return null
}

We’ll use this function to get the display name post-success result requestSignIn and invoke the doOnSucess callback with the display name. When we put all the pieces together, it’ll look as follows:

object GoogleSignInManager {
private lateinit var credentialManager: CredentialManager
private suspend fun googleSignIn(
context: Context,
apiKey: String,
filterByAuthorizedAccounts: Boolean,
doOnSuccess: (String) -> Unit,
doOnError: (Exception) -> Unit,
) {
if (::credentialManager.isInitialized.not()) {
credentialManager = CredentialManager
.create(context)
}
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption
.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(apiKey)
.setAutoSelectEnabled(false)
.build()
val request: GetCredentialRequest = GetCredentialRequest
.Builder()
.addCredentialOption(googleIdOption)
.build()
requestSignIn(
context,
request,
apiKey,
filterByAuthorizedAccounts,
doOnSuccess,
doOnError
)
}
private suspend fun requestSignIn(
context: Context,
request: GetCredentialRequest,
apiKey: String,
filterByAuthorizedAccounts: Boolean,
doOnSuccess: (String) -> Unit,
doOnError: (Exception) -> Unit,
) {
try {
val result: GetCredentialResponse = credentialManager.getCredential(
request = request,
context = context,
)
val displayName = handleCredentials(result.credential)
displayName?.let {
doOnSuccess(displayName)
} ?: doOnError(Exception("Invalid user"))
} catch (e: Exception){
if (e is NoCredentialException && filterByAuthorizedAccounts) {
googleSignIn(
context,
apiKey,
false,
doOnSuccess,
doOnError
)
} else {
doOnError(e)
}
}
}
private fun handleCredentials(credential: Credential): String? {
when (credential) {
// GoogleIdToken credential
is CustomCredential -> {
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
try {
// Use googleIdTokenCredential and extract id to validate and
// authenticate on your server.
val googleIdTokenCredential = GoogleIdTokenCredential
.createFrom(credential.data)
return googleIdTokenCredential.displayName
} catch (e: GoogleIdTokenParsingException) {
println("Received an invalid google id token response $e")
}
} else {
// Catch any unrecognized custom credential type here.
println("Unexpected type of credential")
}
}
else -> {
// Catch any unrecognized credential type here.
println("Unexpected type of credential")
}
}
return null
}
}
Google Button Flow

The approach is pretty much the same, the only difference is that we use GetSignInWithGoogleOption instead of GetGoogleIdOption. Then we have to pass it to the requestSignIn as we did in the above code.

val signInWithGoogleOption: GetSignInWithGoogleOption = 
   GetSignInWithGoogleOption.Builder()
  .setServerClientId(clientId)
  .build()
Handle Sign-Out

It’s obvious to handle the sign-out state, we need to trigger the clearCredentialState function from CredentialManager to notify credential providers that any stored for the app should be cleared. We’ll create one more function in GoogleSignInManager to handle this, have a look:

suspend fun singOut(context: Context) {
    if (::credentialManager.isInitialized.not()) {
        credentialManager = CredentialManager
            .create(context)
    }
    credentialManager.clearCredentialState(ClearCredentialStateRequest())
}
What’s Next:

If you’re interested in what you can do with an authenticated user from an Android app, you can start with Google Drive API, to learn more read the following article:

https://medium.com/android-dev-hacks/integrating-google-drive-api-in-android-applications-18024f42391c?source=post_page—–c54762376170——————————–

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Using annotations in Kotlin has some nuances that are useful to know
READ MORE
blog
One of the latest trends in UI design is blurring the background content behind the foreground elements. This creates a sense of depth, transparency, and focus,…
READ MORE
blog
With JCenter sunsetted, distributing public Kotlin Multiplatform libraries now often relies on Maven Central…
READ MORE
blog
If your gender matches the one that was assigned to you at birth, you…
READ MORE
Menu