Silent sign in to retrieve token with GoogleApiClient

14,472

Solution 1

Yes, answer above is correct. In general, any GoogleApiClient needs to be connected before it can return you any data. enableAutoManage helps you to call connect() / disconnect() automatically during onStart() / onStop(). If you don't use autoManage, you will need to connect() manually.

And even better, you should disconnect in a finally block.

Assuming you are not on the UI thread.

try {
    ConnectionResult result = mGoogleApiClient.blockingConnect();
    if (result.isSuccess()) {
        GoogleSignInResult googleSignInResult =
            Auth.GoogleSignInApi.silentSignIn(googleApiClient).await();
    ...
    }
} finally {
    mGoogleApiClient.disconnect();
}

And also, to clean up your code a little bit: 1. gso built from below configuration is identical to your pasted code above:

GoogleSignInOptions gso =
   new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(SERVER_CLIENT_ID)
        .requestEmail()
        .build();
  1. Based on your current logic, addOnConnectionFailedListener / addConnectionCallbacks doesn't help other than adb log. Maybe just remove them completely?

Solution 2

I found the problem. I was under the impression that the function

OptionalPendingResult<GoogleSignInResult> pendingResult = Auth.GoogleSignInApi.silentSignIn(googleApiClient);

was going to connect the mGoogleApiClient for me (since it returns a pending result). However, that was not the case and in order to solve the above I just needed to add the call

ConnectionResult result = mGoogleApiClient.blockingConnect();

in the beginning of the silentLogin method. (and then of course disconnect later on, and also make sure the call is made in a thread different from main thread)

tada'

Solution 3

To add to the two answers above by Isabella and Ola, if you're using the new sign in lib with Firebase:

FirebaseAuth.getInstance().currentUser?.let{ 
    //create sign-in options the usual way
    val googleSignInClient = GoogleSignIn.getClient(context, gso)
    googleSignInClient.silentSignIn().addOnCompleteListener {
        val account: GoogleSignInAccount? = it.result
        //get user info from account object
    }
}

Also, this can be called from the UI thread. FirebaseAuth.getInstance().currentUser will always return the user object if you have signed in once before.

Share:
14,472

Related videos on Youtube

Ola Melén
Author by

Ola Melén

Updated on June 23, 2022

Comments

  • Ola Melén
    Ola Melén almost 2 years

    I am using "Google Sign-In" in my app. Hence I use the class GoogleApiClient to get the user email and the ID token that I need for my backend.

    When the user signs in, then I have access to an Activity (of course) and I use that Activity to let the GoogleApiClient handle the UI lifecycle stuff by calling builder.enableAutoManage(myActivity, ...)

    This works fine.

    However, at a later stage (several days later), I need to get a new token (for some reason that I will not go into further here). I want to get this token silently without user interaction. However, at the point in my code where I need this new token I have no access to any Activity instance. That means that I am not able to make the call mentioned above i.e. "builder.enableAutoManage". And I have found that if I do not make that very call, then the silent login does not seem to work.

    I have attached the code below. Now, take a look in the "silentLogin" method. As long as the token that I have received as the user did the actual sign in, is younger than one hour, then the statement "pendingResult.isDone" will return true and the cached token can be received. However, if the token that I have received as the user did the actual sign in, is older than one hour, then the call "pendingResult.setResultCallback" is made BUT THE "onResult" method IS NEVER CALLED and I can not get a new token. This problem does not happen if I do exactly the same from an activity (and by that also make the call "builder.enableAutoManage").

    So, does anybody know what I am doing wrong and more importantly - how to solve this problem and get a new token without access to an activity instance?

    I am using com.google.android.gms:play-services-auth:8.4.0

    package com.google.samples.quickstart.signin;
    
    import android.content.Context;
    import android.os.Bundle;
    import android.util.Log;
    
    import com.google.android.gms.auth.api.Auth;
    import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
    import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
    import com.google.android.gms.auth.api.signin.GoogleSignInResult;
    import com.google.android.gms.common.ConnectionResult;
    import com.google.android.gms.common.Scopes;
    import com.google.android.gms.common.api.GoogleApiClient;
    import com.google.android.gms.common.api.OptionalPendingResult;
    import com.google.android.gms.common.api.ResultCallback;
    import com.google.android.gms.common.api.Scope;
    
    /**
     * Use this class to login with google account using the OpenId oauth method.
     */
    public class GoogleLoginStackOverflow {
        private static final String TAG = GoogleLoginIdToken.class.getName();
        private static final String SERVER_CLIENT_ID = "XXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com";
    
        private GoogleApiClient mGoogleApiClient;
        private Context mContext;
    
        private GoogleLoginStackOverflow(Context appContext) {
            this.mContext = appContext;
            createGoogleClient();
        }
    
        /**
         * Performs a silent sign in and fetch a token.
         *
         * @param appContext Application context
         */
        public static void silentLogin(Context appContext) {
            GoogleLoginStackOverflow googleLoginIdToken = new GoogleLoginStackOverflow(appContext);
            googleLoginIdToken.silentLogin();
        }
    
        private void createGoogleClient() {
            GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                    .requestProfile()
                    .requestScopes(new Scope(Scopes.PROFILE))
                    .requestIdToken(SERVER_CLIENT_ID)
                    .requestEmail()
                    .build();
    
            mGoogleApiClient = new GoogleApiClient.Builder(mContext)
                    .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                        @Override
                        public void onConnectionFailed(ConnectionResult connectionResult) {
                            System.out.println("onConnectionFailed  = " + connectionResult);
                            onSilentLoginFinished(null);
                        }
                    })
                    .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                        @Override
                        public void onConnected(Bundle bundle) {
                            System.out.println("onConnected bundle = " + bundle);
                            onSilentLoginFinished(null);
                        }
    
                        @Override
                        public void onConnectionSuspended(int i) {
                            System.out.println("onConnectionSuspended i = " + i);
                            onSilentLoginFinished(null);
                        }
                    }).addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                    .build();
        }
    
        private void silentLogin() {
            OptionalPendingResult<GoogleSignInResult> pendingResult = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
            if (pendingResult != null) {
                if (pendingResult.isDone()) {
                    // If the user's cached credentials are valid, the OptionalPendingResult will be "done"
                    // and the GoogleSignInResult will be available instantly.
                    Log.d(TAG, " ----------------  CACHED SIGN-IN ------------");
                    System.out.println("pendingResult is done = ");
                    GoogleSignInResult signInResult = pendingResult.get();
                    onSilentLoginFinished(signInResult);
                } else {
                    System.out.println("Setting result callback");
                    // If the user has not previously signed in on this device or the sign-in has expired,
                    // this asynchronous branch will attempt to sign in the user silently.  Cross-device
                    // single sign-on will occur in this branch.
                    pendingResult.setResultCallback(new ResultCallback<GoogleSignInResult>() {
                        @Override
                        public void onResult(GoogleSignInResult googleSignInResult) {
                            System.out.println("googleSignInResult = " + googleSignInResult);
                            onSilentLoginFinished(googleSignInResult);
                        }
                    });
                }
            } else {
                onSilentLoginFinished(null);
            }
        }
    
        private void onSilentLoginFinished(GoogleSignInResult signInResult) {
            System.out.println("GoogleLoginIdToken.onSilentLoginFinished");
            if (signInResult != null) {
                GoogleSignInAccount signInAccount = signInResult.getSignInAccount();
                if (signInAccount != null) {
                    String emailAddress = signInAccount.getEmail();
                    String token = signInAccount.getIdToken();
                    System.out.println("token = " + token);
                    System.out.println("emailAddress = " + emailAddress);
                }
            }
        }
    }
    
    • zIronManBox
      zIronManBox over 8 years
      When you say token, May I ask what kind of token?
    • Ola Melén
      Ola Melén over 8 years
      The token that I want from the Google API's is an ID token (I think that is the only token type that you can get by calling GoogleSignInAccount.getIdToken())
  • Creos
    Creos about 8 years
    The google documentation as always fails spectacularly. It is absolutely unclear if apiClient.connect should be invoked before or after an attempted signin. In fact, i think the new model of splitting out singing in and connection has increased complexity, not reduced it. At any rate, thanks Isabella for the answer, this has helped me as well!
  • Ildar Zaripov
    Ildar Zaripov almost 5 years
    Found an article where it is said that GoogleApiClient will be replaced by GoogleSignInClient. See android-developers.googleblog.com/2017/11/…
  • Usman Rana
    Usman Rana over 4 years
    Does it auto picks the current logged in account who's idToken is expired?