Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid 'Failed to retrieve access token: { "error" : "invalid_grant" }' in offline GAE cron tasks?

This post is a followup to How to make 'access_type=offline' / server-only OAuth2 operations on GAE/Python. The http = credentials.authorize(httplib2.Http()) part no longer fails when testing, but it seems it still does when run by GAE's cron, where it's unable to refresh my access_token :

  1. I can manually run my job by calling /fetch, say at 11:45.
  2. Scheduling immediately a /cronfetch job at 11:55 works then, without any access_token issue.
  3. But then, I woke up this morning seeing that the same /cronfetch task (same except the timing, which is at 01:00 for my non-test daily task) failed:

    I 2013-06-10 05:53:51.324 make: Got type <class 'google.appengine.api.datastore_types.Blob'>
    I 2013-06-10 05:53:51.325 validate: Got type <class 'oauth2client.client.OAuth2Credentials'>
    I 2013-06-10 05:53:51.327 URL being requested: https://www.googleapis.com/youtube/v3/playlists?alt=json&part=snippet%2Cstatus
    I 2013-06-10 05:53:51.397 Refreshing due to a 401
    I 2013-06-10 05:53:51.420 make: Got type <class 'google.appengine.api.datastore_types.Blob'>
    I 2013-06-10 05:53:51.421 validate: Got type <class 'oauth2client.client.OAuth2Credentials'>
    I 2013-06-10 05:53:51.421 Refreshing access_token
    I 2013-06-10 05:53:51.458 Failed to retrieve access token: { "error" : "invalid_grant" }
    I 2013-06-10 05:53:51.468 make: Got type <class 'google.appengine.api.datastore_types.Blob'>
    I 2013-06-10 05:53:51.468 validate: Got type <class 'oauth2client.client.OAuth2Credentials'>
    I 2013-06-10 05:53:51.471 validate: Got type <class 'oauth2client.client.OAuth2Credentials'>
    I 2013-06-10 05:53:51.471 get: Got type <class 'oauth2client.appengine.CredentialsModel'>
    E 2013-06-10 05:53:51.480 invalid_grant Traceback (most recent call last): File "/python27_runtime/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1535, in
    

This Getting "invalid_grant" error on token refresh mailing list message (+ SO post 1, SO post 2, SO post 3) look similar to my problem, but it seems to be happening with a access_type=online token. In my case I just use the default access_type=offline, and I see the "Perform these operations when I'm not using the application" mention in the initial access request.

I just re-scheduled a cron run at 08:25 (taking care not to launch a manual one) with debug print statements that I committed to GitHub for you. Here is what I get, it is similar but not identical (Note the few last lines seem ordered incorrectly, I definitely am not doing OAuth2 stuff in create_playlist until all sources are read). So ignoring the skewed order (GAE logging artifact?), it seems my http = credentials.authorize(Http()) call in create_playlist(self), currently at line 144 is wrong:

    ...
    E 2013-06-10 08:26:12.817 http://www.onedayonemusic.com/page/2/ : found embeds ['80wWl_s-HuQ', 'kb1Nu75l1vA', 'kb1Nu75l1vA', 'RTWcNRQtkwE', 'RTWcNRQtkwE', 'ZtDXezAhes8', 'ZtDXezAhes8', 'cFGxNJhKK9c', 'cFGxNJhKK9c'
    I 2013-06-10 08:26:14.019 make: Got type <class 'google.appengine.api.datastore_types.Blob'>
    I 2013-06-10 08:26:14.020 validate: Got type <class 'oauth2client.client.OAuth2Credentials'>
    I 2013-06-10 08:26:14.022 URL being requested: https://www.googleapis.com/youtube/v3/playlists?alt=json&part=snippet%2Cstatus
    I 2013-06-10 08:26:14.100 Refreshing due to a 401
    I 2013-06-10 08:26:14.105 make: Got type <class 'google.appengine.api.datastore_types.Blob'>
    I 2013-06-10 08:26:14.106 validate: Got type <class 'oauth2client.client.OAuth2Credentials'>
    I 2013-06-10 08:26:14.106 Refreshing access_token
    E 2013-06-10 08:26:18.994 Deadline exceeded while waiting for HTTP response from URL: https://accounts.google.com/o/oauth2/token Traceback (most recent call last): File "/pyt
    E 2013-06-10 08:26:18.996 http://www.onedayonemusic.com/page/3/ : found embeds ['80wWl_s-HuQ', '6VNu2MLdE0c', '6VNu2MLdE0c', 'YwQilKbK9Mk', 'YwQilKbK9Mk', 'KYdB3rectmc', 'KYdB3
    E 2013-06-10 08:26:18.996 crawl_videos end
    E 2013-06-10 08:26:18.996 create_playlist start
    E 2013-06-10 08:26:18.996 create_playlist got creds
    E 2013-06-10 08:26:18.996 create_playlist authorized creds

→ Why does the cron job work 5min after a manual run, but fails 6hours later? I thought the refresh token never expired. What am I doing wrong?

Note that's my first GAE work, and my second Python program at all, general code review/advice is very welcome but please be gentle :)

The code is on GitHub and my instance can be reached at dailygrooves.org. Thanks for your help!

like image 567
Ronan Jouchet Avatar asked Jun 10 '13 12:06

Ronan Jouchet


People also ask

What does Invalid_grant mean?

The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.

How do I get the access token from refresh token?

To get a refresh token , you must include the offline_access scope when you initiate an authentication request through the /authorize endpoint. Be sure to initiate Offline Access in your API.

How long do Google access tokens last?

A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days. There is currently a limit of 100 refresh tokens per Google Account per OAuth 2.0 client ID.


1 Answers

An invalid_grant is returned when the refresh token can't be used to get a new access token from the current user. This is happening to you because the stored Credentials object has a null refresh token, i.e.

>>> credentials.refresh_token is None
True

As mentioned in the NOTE in How to make 'access_type=offline' / server-only OAuth2 operations on GAE/Python?:

If a user has already authorized your client ID, the subsequent times you perform OAuth for these users they will not see the OAuth dialog and you won't be given a refresh token.

You need to make sure your Credentials are stored with a valid refresh token and the easiest way to do this, as mentioned in your last question as well as in all 3 questions you linked to is to use approval_prompt=force when creating your OAuth2WebServerFlow or OAuth2Decorator object (whichever you are using).

like image 91
bossylobster Avatar answered Sep 24 '22 23:09

bossylobster