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:
- There can be Android users who don’t have a Twitter or Facebook account but not a Google account.
- 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 library. You 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:
GetGoogleIdOption
: This is used when you want to have customer UI for the entry point of the Sign-In flow.- 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
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:
This article is previously published on proandroiddev.com