5

I am working with authenticating via Google+ according to the following: https://developers.google.com/+/mobile/android/sign-in

Most of this process seems fine. The problem I'm having is that we need to get a "one-time authorization code" so that our backend servers can perform certain requests on behalf of the user, with their permission. This is covered in the section "Enable server-side api access for your app". However, for a number of reasons, our servers can cause the login to fail, even if the authorization code is valid (e.g. the user doesn't have an account corresponding to the google+ account on our servers yet, in which case they can make one).

If this happens, we might need them to login again at a later time. What I'm finding, though, is that when I perform the second login with google+, it gives me the same authorization code, even if it's already been used by our servers. I've tried disconnecting and reconnecting to the google client api, and calling GoogleApiClient.clearDefaultAccountAndReconnect(), but no matter what I do, I seem to end up with the same authorization code. This, of course, is rejected by the server when it tries to use it, since it's already been used.

I'm wondering what I'm doing wrong here. I have the following method, which is called during the initial authentication process, and then again if a response status of 500 is detected from our server (indicating the previous call failed, presumably because the code has already been used):

  private void dispatchGooglePlusAuthCodeAcquisition() {
    AsyncTask<Void, Void, String> authAcquisition = new AsyncTask<Void, Void, String>() {
      @Override
      protected String doInBackground(Void... params) {
        Bundle authPreferences = new Bundle();
        mUserPermissionNeededForAuthCode = false;
        authPreferences.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES,
                                "");
        String scopesString = Scopes.PROFILE;
        WhenIWorkApplication app = (WhenIWorkApplication)WhenIWorkApplication.getInstance();
        String serverClientID = app.getGoogleOAuthClientIDForPersonalServer();
        String scope = "oauth2:server:client_id:" + serverClientID + ":api_scope:" + scopesString;
        String code = null;
        authPreferences.putBoolean(GoogleAuthUtil.KEY_SUPPRESS_PROGRESS_SCREEN, true);

        try {
          code = GoogleAuthUtil.getToken(
            mActivity,
            Plus.AccountApi.getAccountName(mGoogleApiClient),
            scope,
            authPreferences
          );
        } catch (IOException transientEx) {
          // network or server error, the call is expected to succeed if you try again later.
          // Don't attempt to call again immediately - the request is likely to
          // fail, you'll hit quotas or back-off.
          Log.d(LOGTAG, "Encountered an IOException while trying to login to Google+."
                                   + " We'll need to try again at a later time.");
        } catch (UserRecoverableAuthException e) {
          mUserPermissionNeededForAuthCode = true;
          // Requesting an authorization code will always throw
          // UserRecoverableAuthException on the first call to GoogleAuthUtil.getToken
          // because the user must consent to offline access to their data.  After
          // consent is granted control is returned to your activity in onActivityResult
          // and the second call to GoogleAuthUtil.getToken will succeed.
          if (!mGooglePlusPermissionActivityStarted) {
            mGooglePlusPermissionActivityStarted = true;
            mActivity.startActivityForResult(e.getIntent(), RESULT_CODE_AUTH_CODE);
          }
        } catch (GoogleAuthException authEx) {
          // Failure. The call is not expected to ever succeed so it should not be
          // retried.
          Log.e(LOGTAG, "Unable to authenticate to Google+. Call will likely never"
                                   + " succeed, so bailing.", authEx);
        }

        return code;
      }

      @Override
      protected void onPostExecute(String aResult) {
        if (aResult != null) {
          // We retrieved an authorization code successfully.
          if (mAPIAccessListener != null) {
            mAPIAccessListener.onAuthorizationCodeGranted(aResult);
          }
        } else if (!mUserPermissionNeededForAuthCode) {
          // If this is the case, then we didn't get authorization from the user, or something
          // else happened.
          if (mAPIAccessListener != null) {
            mAPIAccessListener.onAuthorizationFailed();
          }

          Log.d(LOGTAG, "Unable to login because authorization code retrieved was null");
        }
      }
    };

    authAcquisition.execute();
jwir3
  • 6,019
  • 5
  • 47
  • 92

1 Answers1

3

So, the answer to this was a lot simpler than I imagined. Apparently, there is aclearToken() method on the GoogleAuthUtil class:

http://developer.android.com/reference/com/google/android/gms/auth/GoogleAuthUtil.html#clearToken%28android.content.Context,%20java.lang.String%29

public static void clearToken (Context context, String token)

Clear the specified token in local cache with respect to the Context. Note that the context must be the same as that used to initialize the token in a previous call to getToken(Context, String, String) or getToken(Context, String, String, Bundle).

Parameters

context Context of the token.
token The token to clear.

Throws

GooglePlayServicesAvailabilityException
GoogleAuthException
IOException

Calling this method before attempting to re-authenticate causes Google to generate a new one-time authorization token.

jwir3
  • 6,019
  • 5
  • 47
  • 92
  • That's what I'm doing, but I still get the same authorization code for 10 minutes. Did this return a new authorization code immediately for you? – Jonathan May 26 '15 at 12:52
  • @Jonathan So, what I do is I have a `GooglePlusAuthUtil` class that I store the authorization token in as a member variable. If the authorization token is non-null (that is, I've already seen a token come across the wire), I call `clearToken()`, and then I call `getToken()` again and store the result in the member variable. This does regenerate the token for me, yes. – jwir3 May 26 '15 at 20:08
  • @Jonathan Sorry, I forgot to mention a crucial part - you have to send the token with the `clearToken()` method call. Is it possible that you're attempting to call `getToken()` before the `clearToken()` method completes (i.e. in an asynchronous way)? – jwir3 May 26 '15 at 20:09
  • 1
    [@jwir3](http://stackoverflow.com/users/281460/jwir3) I'm successfully regenerating my authorization code using this method. But, I've also found that doing it this way will lead to a UserRecoverableAuthException when calling getToken(), and I will have approve the consent dialog again. I would ideally want this to be one click signin after the user has signed and given consent the first time. Any way around this? – waynesford Aug 03 '15 at 19:14