21

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

2
  • please edit your question and include your code. Commented Dec 8, 2023 at 15:10
  • @LindaLawton-DaImTo Updated the Q to include a link to my code.
    – Tigerrrrr
    Commented Dec 8, 2023 at 15:26

1 Answer 1

23

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:

  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.
44
  • 1
    Yes, please provide some sample code. "SigninWithGoogle" means that you are asking an ID token from Google that contains the information for the single account that user has selected from the Google accounts on the device, and given consent to be shared. So "SigninWithGoogle" and Google ID Token are not two distinct things. If you have 6 Google accounts on the device and if you have set things up correctly and are making the correct request, you should see those 6 accounts. Please also indicate if you're using a device running Android U or a lower version.
    – Ali Naddaf
    Commented Dec 12, 2023 at 17:50
  • 1
    It looks like you copy\pasted and didn't even test this code. Method "addGetCredentialOption()" is not part of class "GetCredentialRequest.Builder". Please refer: developer.android.com/reference/androidx/credentials/…
    – rayzinnz
    Commented Jan 3 at 20:09
  • 1
    Thanks for pointing that out, I updated to addGetCredentialOption().
    – Ali Naddaf
    Commented Jan 3 at 22:11
  • 1
    Thanks @AliNaddaf this is great information. To avoid having a user sign in everytime after the 1 hour expiration I would need to use the legacy Authorization API? I'm confused as Credential Manager is suppose to replace this no? Commented Mar 7 at 6:58
  • 3
    Thanks. Android APIs are simply TERRIBLE! Even with far less experience with Obj-C, implement such thing is way easier. The Android is not only harder but unstable, in four years it almost have 3 distinct APIs for a simple "sign-in". The API is so terrible that they say "GetSignInWithGoogleOption instead of GetGoogleIdOption", but this second one lacks "setServerClientId" and other options (the client id is on the constructor). Seriously, as far I hate Apple, their APIs are way better.
    – Inkeliz
    Commented Mar 13 at 23:22

Not the answer you're looking for? Browse other questions tagged or ask your own question.