Search code examples
androidgoogle-oauthgoogle-signingoogle-one-tapandroid-credential-manager

Credential Manager - How do I create a "SignInWithGoogle" credential?


I'm trying to set up Google's One-Tap but with the new, all-in-one, Credential Manager.

However, after crawling through the (badly written) documentation, I've come to a halt. Upon "Signing in with Google", everything is fine until I get a "NoCredentialException: no credentials available", which makes sense.

But then... how do I create a credential?

Google provides examples to create credentials for both passwords and passkeys but I can't find any information on creating credentials for the "Sign In With Google" button (anywhere on the internet).

"The Sign In With Google Button is supported by Credential Manager with the newest Google ID helper library"

(As stated by Android)

So, I use <CredentialManager>.createCredentialAsync() because that's what Google used during the examples they provided (and explicitly told to do here).

However, Android's createCredentialAsync requires a CreateCredentialRequest and there are only three types that it accepts: "CreatePasswordRequest", "CreatePublicKeyCredentialRequest" and "CreateCustomCredentialRequest".

This is where "Google's ID helper library" mentioned in the quote above is supposed to come in handy. The library has the classes GetGoogleIdOption and GetSignInWithGoogleOption which are both subclasses of GetCustomCredentialOption.

The question now is how am I supposed to get myself a CreateCustomCredentialRequest class (or a subclass of it) for my <CredentialManager>.createCredentialAsync() method.

Google's "newest ID helper library" doesn't provide:

  1. A subclass of CustomCredential & it's Builder for SignInWithGoogle (it does for GoogleIdToken)
  2. A ridiculously long CreateSignInWithGoogleRequest class (or a CreateGoogleIdRequest class) that's a subclass of the CreateCustomCredentialRequest class.

Therefore, since I'm stuck on how I'm supposed to get this CreateCustomCredentialRequest, I'm not sure how I'm supposed to "Integrate Credential Manager with Sign in with Google" either.

Before I end, I want to mention one last thing. In the "Sign up with Google" section, it says:

If no results are returned after setting setFilterByAuthorizedAccounts to true while instantiating the GetGoogleIdOption request and passing it to GetCredentialsRequest, it indicates that there are no authorized accounts to sign in. At this point, you should set setFilterByAuthorizedAccounts(false) and call Sign up with Google.

This doesn't help me because:

  1. This only references GetGoogleIdOption and not GetSignInWithGoogleOption.
  2. There's no explanation on how to "call Sign up with Google".

Afterwards, it says:

Once you instantiate the Google sign-up request, launch the authentication flow similarly as mentioned in the Sign in with Google section.

Is there supposed to be a GetGoogleSignUpRequest class?

Is there anything I'm missing? Did I make a stupid mistake somewhere? Any help on this would be great!

For extra context, I've provided the entirety of my code here: https://www.online-java.com/VjQw6cTKig


Solution

  • Let me try to clarify a few things and try to answer your questions and issues. From the original post, it seems to me that you're trying to sign in your users using Google ID tokens, please correct me if I am wrong. In that case, you don't need to "create" a Google credential. When you try to sign in with a Google account, you rely on the Google accounts that are on the Android device. If there are no Google accounts on the device (or there are, but they need reauthentication, for example if their passwords were changed elsewhere), then the user needs to add a Google account to the device, or reauth the existing accounts first. Now, assuming there are working Google accounts on the device, you can use the Credential Manager APIs (similar to the One Tap APIs) to help users sign-in to your app; the flow is outlined in details here but I will briefly go through them to make it clear.

    After declaring the dependencies (remember that you'd need to include the dependency on the CredentialManager AND the com.google.android.libraries.identity.googleid to be able to use Google ID tokens for your sign-in. Then the flow goes as (make sure you use androidx.* version of classes instead of the ones from the Android framework):

    1. Build a request:

      GetGoogleIdOption googleIdOption = new GetGoogleIdOption.Builder()
         .setFilterByAuthorizedAccounts(true)
         .setServerClientId(WEB_CLIENT_ID)
         .setNonce(NONCE)
         .build();
      GetCredentialRequest request = new GetCredentialRequest.Builder()
        .addCredentialOption(googleIdOption)
        .build();
      
    2. Make the API call:

      credentialManager.getCredentialAsync(
        requireActivity(),
        request,
        cancellationSignal,
        <executor>,
        new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
      @Override
      public void onResult(GetCredentialResponse result) {
        handleSignIn(result);
      }
      
      @Override
      public void onError(GetCredentialException e) {
        handleFailure(e);
      }
      

      } );

    3. Retrieve the credentials that the user selected:

      public void handleSignIn(GetCredentialResponse result) {
         Credential credential = result.getCredential();
      
         if (credential instanceof CustomCredential) {
           if (GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.getType())) {
             try {
               GoogleIdTokenCredential googleIdTokenCredential = GoogleIdTokenCredential.createFrom(((CustomCredential) credential).getData());
             } catch (GoogleIdTokenParsingException e) {
               Log.e(TAG, "Received an invalid google id token response", e);
             }
          }
      

    Now a few important things that may help with your app and also user experience:

    • In step 1 above, you set the filterByAuthorizedAccounts to true. That means users will see only those accounts that have already been used for your app and have given grants for sharing the usual profileId, email, and such with your app. The reason we insist on making the first call with that setting is the following: if your user has, say, 2 Google accounts on the device and had previously used the first account successfully to sign into your app, in subsequent sessions, you probably want the same user to pick the same Google account to avoid creating a duplicate account by choosing the second account. This can be accomplished by setting that attribute to true: it only shows those accounts that have been used before. If such an account exists and the user selects that, the user basically "signs-in" back to your app (see the next bullet point for "sign-up").
    • If you get "No credentials available" response by following the above steps, you then make a second call but this time, in your request, you set the filterByAuthorizedAccounts to false. This means: "show all the Google accounts on the device to the user". In most cases, it means that this user is a new user for your app, so it amounts to basically "Signing-up" with your app.
    • You may get the "No credentials available" response again; this would most likely mean that there are no Google accounts on the device (see my earlier comments).
    • Very importantly, we strongly recommend that the above API calls be made in your app as soon as the user lands on your app, without any user interaction (i.e without a need for the user to click on a button). This is very different from making these calls if a user taps on the "Sign-in with Google" button; there is a different API (exposed through the CredentialManager as well) that I will explain below briefly, that you can call when you want to react to tapping on "Sign-in with Google" but the original steps outlined above result in showing a bottomsheet to the user while the proper treatment of clicking on a "Sign-in with Google" button should pop-up a regular dialog. The bottomsheet flow can also be called if, for example, users of your app can do certain things in your app without signing-in to your app (say, some free content) and then when they want to access, say, a paid content, you can programmatically call the above APIs to show a bottomsheet before they can move forward.
    • Using the CredentialManager APIs, similar to the older One Tap APIs, you can easily include more types of credentials; for example you can consider adding passkeys as another secure authentication method to your app.

    How to use the APIs for "Sign-in with Google" button.

    To do this, you can use the following steps:

    1. You create the same type of request but this time you use GetSignInWithGoogleOption class to build the options, using its Builder.

    2. The rest will be identical to the previous case: you call the same API (using the new options) and you extract the returned credential through the same process.

    There are a few differences worth noting when using the "button" flow:

    • As was mentioned above, the UI shown to the user is different; instead of a bottomsheet, it will be a regular dialog.
    • It allows your users to add a Google account to their devices if there is none (or if there are some but they don't see the account they are interested in), and also allows your users to re-authenticate their accounts, if needed. These features are missing from the bottomsheet flow intentionally: tapping on a "Sign-in with Google" button clearly shows that the intention of the user is to use Google accounts so if there is none, it makes sense to help them create one but in the bottomsheet flow, since that should pop-up automatically by developer when users land on the app (based on our strong recommendation), it is less clear if the user had intended to do so or not.
    • You cannot mix in other types of credentials in your request.