Search code examples
androidauthenticationgoogle-signingoogle-identitycredential-manager

Get serverAuthCode using Credential Manager


We are updating our application to use the CredentialManager for logging in, but we are not finding a way to get the serverAuthCode from this new implementation. We need this information to maintain compatibility with our backend, which expects this information instead of the idToken.

Our old implementation:

val task = GoogleSignIn.getSignedInAccountFromIntent(activityResult.data)
val account = try {
    task.getResult(ApiException::class.java)
} catch (e: ApiException) {
    // Something treatment here
    null
}
val serverAuthCode = account?.serverAuthCode
// endpoint call

New implementation:

val googleIdOption = GetSignInWithGoogleOption.Builder("our serverClientId").build()
val request = GetCredentialRequest.Builder()
    .addCredentialOption(googleIdOption)
    .build()
val credentialManager = CredentialManager.create(context)
val response = credentialManager.getCredential(context, request)
val credential = response.credential
if (credential is CustomCredential && credential.type == TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
    try {
        val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
        // rest of the code and endpoint call

As I mentioned above, in this new implementation, it is not possible to get the serverAuthCode, only the idToken.

How should we go about getting the serverAuthCode and maintaining compatibility with our backend?

Here’s how I’m handling the authorization:

val authorizationLauncher = rememberAuthorizationLauncher()

val requestedScopes = listOf(
    Scope(Scopes.PLUS_ME),
    Scope(Scopes.PROFILE),
    Scope("https://www.googleapis.com/auth/user.birthday.read"),
    Scope("https://www.googleapis.com/auth/user.gender.read"),
    Scope("https://www.googleapis.com/auth/userinfo.email")
)

val authorizationRequest = AuthorizationRequest.builder().setRequestedScopes(requestedScopes).build()

Identity.getAuthorizationClient(context)
    .authorize(authorizationRequest)
    .addOnSuccessListener { authorizationResult ->
        if (authorizationResult.hasResolution()) {
            authorizationResult.pendingIntent?.intentSender?.let { intentSender ->
            authorizationLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
            }
        } else {
            authorizationResult.serverAuthCode // always null
        }
    }

@Composable
private fun rememberAuthorizationLauncher(
    activity: Activity = LocalActivity.current
) = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.StartIntentSenderForResult()
) { activityResult ->
    if (activityResult.resultCode == Activity.RESULT_OK) {
        Identity.getAuthorizationClient(activity).getAuthorizationResultFromIntent(activityResult.data)
    }
}


Solution

  • The solution for this is to request offline access when building the authorization request:

    AuthorizationRequest.builder()
        .setRequestedScopes(requestedScopes)
        .requestOfflineAccess(SERVER_CLIENT_ID) // THIS!
        .build()
    

    See a working sample here