6
votes

I am correctly scoping for offline access and am storing that. Every 60 minutes, when needed, I retrieve a new access_token. Code has not changed, but what is odd is that when he first went through the authorization.

client_id       ="xxxxx.apps.googleusercontent.com"
client_secret   ="xxxxxxxxxxxxxxxxxxxx"
refresh_token   ="xxxxxxxxxxxxxxxxxxx"
response        = oauth2a.RefreshToken(client_id,client_secret,refresh_token)

def RefreshToken(client_id, client_secret, refresh_token):
  params = {}
  params['client_id'] = client_id
  params['client_secret'] = client_secret
  params['refresh_token'] = refresh_token
  params['grant_type'] = 'refresh_token'
  request_url = AccountsUrl('o/oauth2/token')

  response = urllib.urlopen(request_url, urllib.urlencode(params)).read()
  return json.loads(response)

The response is always {u'error': u'invalid_grant'}. I have attempted this on three different machines,and Also Get HTTPError: HTTP Error 400: Bad Request

5

5 Answers

15
votes

Invalid_grant error has two common causes.

  1. Your server’s clock is not in sync with NTP. (Solution: check the server time if its incorrect fix it. )
  2. The refresh token limit has been exceeded. (Solution: Nothing you can do they cant have more refresh tokens in use) Applications can request multiple refresh tokens. For example, this is useful in situations where a user wants to install an application on multiple machines. In this case, two refresh tokens are required, one for each installation. When the number of refresh tokens exceeds the limit, older tokens become invalid. If the application attempts to use an invalidated refresh token, an invalid_grant error response is returned. The limit for each unique pair of OAuth 2.0 client and is 25 refresh tokens (note that this limit is subject to change). If the application continues to request refresh tokens for the same Client/Account pair, once the 26th token is issued, the 1st refresh token that was previously issued will become invalid. The 27th requested refresh token would invalidate the 2nd previously issued token and so on.
3
votes

I've had the same issue(windows OS). and I've fixed that by just updating the time server synchronization and it works now perfectly.

enter image description here

1
votes

After finding that the OAuth2 redirect was coming back without a refresh token set, I found my fix to be in this thread https://github.com/google/google-api-python-client/issues/213. Summarized, add prompt = 'consent' to the exchange flow:

flow.params['prompt'] = 'consent'
0
votes

You might want to try adopting the approach used in the Google+ Python Hybrid auth sample and let the library manage the OAuth 2 tokens. A full example is here:

https://developers.google.com/+/quickstart/python

In that sample, code exchange is performed using Google's library and the credentials are stored in a basic session on success (you should use a DB in practice):

# Retrieve authorization code from POST data
code = request.data

try:
  # Upgrade the authorization code into a credentials object
  oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='')
  oauth_flow.redirect_uri = 'postmessage'
  credentials = oauth_flow.step2_exchange(code)
except FlowExchangeError:
  response = make_response(
    json.dumps('Failed to upgrade the authorization code.'), 401)
  response.headers['Content-Type'] = 'application/json'
  return response

#... other code ...
session['credentials'] = credentials

Later on, you can just retrieve the credentials (from the session or db) and the Python library does the rest.

"""Get list of people user has shared with this app."""
credentials = session.get('credentials')
# Only fetch a list of people for connected users.
if credentials is None:
  response = make_response(json.dumps('Current user not connected.'), 401)
  response.headers['Content-Type'] = 'application/json'
  return response
try:
  # Create a new authorized API client.
  http = httplib2.Http()
  http = credentials.authorize(http)
  # Get a list of people that this user has shared with this app.
  google_request = SERVICE.people().list(userId='me', collection='visible')
  result = google_request.execute(http=http)

  response = make_response(json.dumps(result), 200)
  response.headers['Content-Type'] = 'application/json'
  return response
except AccessTokenRefreshError:
  response = make_response(json.dumps('Failed to refresh access token.'), 500)
  response.headers['Content-Type'] = 'application/json'
  return response
0
votes

I had a similar issue and was getting an "invalid_grant" response. Turns out I was making the initial OAuth call with grant_type="client_credentials" and I should have been making it using grant_type="password". Wanted to share the solution which worked for me.

More explanation between the grant_types could be found below

Difference between grant_type=client_credentials and grant_type=password in Authentication Flow?