2

[- Summary -]

Problem: I am trying to implement a Google+ Sign-in with server-side API access for an Android app. I am encountering a problem where sometimes offline access is not granted by the temporary access code, resulting in an invalid_grant error on the backend.

Question: How can I make sure, on the client side, that the access code that is forwarded to the server includes an offline access permission?

[ Elaboration ]

During a Google+ OAuth flow in Android, GoogleAuthUtil.getToken(...) returns a temporary auth code that is later exchanged for an access token to the requested authorization scopes.

It is mentioned (at the time of writing of this post) in Google's own code example that:

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.

Over the course of development, I encountered scenarios where the "offline access" dialog pops (and fails to return a code), where it doesn't pop (and returns a broken code), where it pops and even though the user consents the code is still wrong... and also successful attempts. The app behavior seems to alternate between these scenarios even when introducing seemingly auth-unrelated modifications. {Edit1: Upon further reading, I think that the seemingly arbitrary behavior of the flow stems from old codes being stored inside the AccountManager, as discussed here}

I have also encountered the familiar behavior of peculiar {read: poorly synchronized?} app-flow when debugging (as opposed to running) asynchronous code (because debugging allows tasks running on the UI thread to conclude before\after tasks in the asynchronous thread (see section on "Order of execution") ).

So far I've (implicitly) only treated the case where the user is "connected" to the app (i.e. authorized the app in the past without revoking its access, as can be found in the Manage apps page), however, this problem is exacerbated for new users (since they are presented with two different permission dialogs in the first place).

I am really puzzled on how this entire process should be coded properly {this is why I didn't bother including my own code}, and whether it is truly this complicated or it's just me. For me, it is most important to understand why this happens and what can be done to enforce a well-architected and uniform execution flow while avoiding blindly copying code (which got me into this mess in the first place :P).

Currently my best guess is to somehow get the "latest granted permission of the oauth dialog" and raise flag accordingly.

Additional Information:

Theories as to why this happens:

  1. AsyncTask concurrency - the various checks for the validity of the auth code attempt to access static variables that may be changed by a task running on a different thread (UI vs. async.) {if it is the case - it counter-intuitively doesn't result in exceptions}
  2. Faulty logic - assuming that a valid code appears on the 2nd attempt (or any other number thereof) is faulty logic for checking code validity.

Attempts\ideas to solve this:

  1. Having a flag in place that tracks whether it's the 1st or 2nd time the auth code was received. {Attempt didn't work, probably due to concurrency - see above.}
  2. Obtaining "currently granted permissions" (from the OS, Google utils, AccountManager or etc.), and checking that offline access is present there.
  3. Obtaining the latest permission granted from the Intent contained in UserRecoverableAuthException (during the host activity's onActivityResult, in the task's onPostExecute or elsewhere). {Not tested yet}

Related questions (random order):

Google+ Sign-in for Android - Google Permissions Activity Result Codes

Android Google+ integration - repeated UserRecoverableAuthException

Google Plus Single Sign On Server Flow - Google_AuthException Error fetching OAuth2 access token, message: 'invalid_grant'

Community
  • 1
  • 1
Dev-iL
  • 23,742
  • 7
  • 57
  • 99

0 Answers0