3

I have looked at the other question regarding this topic and it doesn't seem to match my error. I'm getting the error when running Google Sheets APIv4:

raise HttpAccessTokenRefreshError(error_msg, status=resp.status) HttpAccessTokenRefreshError: invalid_grant

Error occurs on the line service.spreadsheets().values().get(spreadsheetId=key, range=ranges).execute()

This error only pops up sometimes. If I don't do anything and just run the code again. It will take me through the authentication flow process again and I get

Authentication successful. Storing credentials to C:\Users\jason\.credentials\sheets.googleapis.com-python-quickstart.json

After which, I can run any code for a while until the same HttpAccessTokenRefreshError: invalid_grant pops up again and I have to reauthenticate again.

How do I prevent this?

I'm using the code found developers.google.com/sheets/api/quickstart/python.

I've tried to use ntp to sync time with the following

import time
import os
try:
    import ntplib
    client = ntplib.NTPClient()
    response = client.request('pool.ntp.org')
    os.system('date ' + time.strftime('%m%d%H%M%Y.%S',time.localtime(response.tx_time)))
except:
    print('Could not sync with time server.')

print('Done.')

but getting:

The system cannot accept the date entered.
Enter the new data: (mm-dd-yy)

After I enter the current date, nothing happens.

I have also looked at this page. https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35#.5utz2vcn6

This problem also arises when I run code that is longer than 1 hour to finish. On the refresh token. It always bomb.

Now I'm thinking, tokens granted only lasts one hour and on refreshes, it always bombs.

I have posted the code for connecting:

class APIv4:
    def __init__(self):
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?'
                    'version=v4')
    self.service = discovery.build('sheets', 'v4', http=http,
                              discoveryServiceUrl=discoveryUrl)

def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir,
                                   'sheets.googleapis.com-python-quickstart.json')

    store = Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else: # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials
jason
  • 3,811
  • 18
  • 92
  • 147
  • If you are having trouble with OAuth, try using API keys as this and OAuth are both [supported types of credentials by Sheets API](https://developers.google.com/sheets/api/guides/authorizing#APIKey). After you have an API key, your application can append the query parameter `key=yourAPIKey` to all request URLs. – Mr.Rebot Mar 06 '17 at 07:25
  • @Mr.Rebot Could you please give me some sample code on how to set this up? I don't understand the service build and how it works with google sheet APIv4. I followed your link to get a API key from my console in credentials. I'm not sure if this would work for me, because my data is not public. It says it does not access any private user data. – jason Mar 07 '17 at 02:59
  • @Mr.Rebot "try using API keys as this and OAuth are both supported types of credentials by Sheets API." is NOT correct. API key will only work if the spreadsheet is public. – pinoyyid Mar 10 '17 at 16:07

2 Answers2

2

As a general idea, there seems to be a problem with the refresh of your access token in between calls.

That either means that some of your credentials are not passed correctly or there is some problem with your local machine's time (although seems less likely than the first option)

I suggest researching the possibilities stated in this issue: https://github.com/google/oauth2client/issues/451:

  1. (less likely) Why don't you try to force a clock update with ntpdate. Install ntp and give it a try, because a user stated that worked for him
  2. Ok. After a loong research I guess I found out the problem. In fact, refresh_token was missing from the user credential, but the issue was tricky.

    The refresh token is given for the FIRST time when the application asks the user for permissions. The refresh token is given ONLY IF the flow's step 1 includes the parameters approval_prompt="force"

    For some reason the user (me) hadn't got refresh_token in user's credentials, so I revoked permissions from the application on My Account -> Security -> Applications, and restarted the OAuth dance again. Now I got refresh_token.

Update for #2 option:

Following this guide and the recommendation stated above, I believe that you must add to this code snippet (taken directly from the guide):

# Create a state token to prevent request forgery.
# Store it in the session for later validation.
state = hashlib.sha256(os.urandom(1024)).hexdigest()
session['state'] = state
# Set the client ID, token state, and application name in the HTML while
# serving it.
response = make_response(
    render_template('index.html',
                    CLIENT_ID=CLIENT_ID,
                    STATE=state,
                    APPLICATION_NAME=APPLICATION_NAME))

the prompt=consent line and then execute the third step of the quoted response above (option 2).

Another option is to use approval_prompt=force but you must choose between the two, because they don't work well together.

Good luck :)

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
  • can you break #2 into steps and be more clear, i don't understand it. – jason Mar 09 '17 at 00:26
  • yeah. http://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token/10857806#10857806. this is the answer for me. I added the `offline` and `force` properties. And now running past 1 hour. – jason Mar 10 '17 at 23:41
  • Good to know mate :) – John Moutafis Mar 11 '17 at 11:10
  • @ John Moutafis. Yeah the code work so well that I'm some how hitting a quota. http://stackoverflow.com/questions/42735670/google-sheets-api-v4-httperror-429-resource-has-been-exhausted-e-g-check-qu – jason Mar 11 '17 at 13:23
1

Some thoughts ....

The timekit.io blog link you posted is quite good in running through the alternatives. In your case, it doesn't sound like it's time related and ntp is overkill. As long as your time is more or less current, you'll be fine.

Your code runs, then fails after an hour with "invalid grant". That means that something is trying to use a Refresh Token to generate a new Access Token and failing. If you are doing your own refresh, obviously check that you are correctly retrieving and using the Refresh Token. My guess is that you're relying on the Google python library to do this (ugh I hate libraries lol).

So, for me the most likely causes are:-

  1. The Refresh Token isn't being correctly saved and restored to use for the refresh
  2. The Refresh Token is stale (ie. more than 25 Refresh Tokens old). This can happen if you run repeated tests during development. Always make sure you are using the most recent RT.
  3. The Refresh Token is null because it is only provided the first time a user authorizes your application. Try going into https://myaccount.google.com/permissions?pli=1 and removing permission to your app, then start again.

If you can capture an http trace, it will help a lot in debugging. See if the python library has any debug/log features you can turn on (I know the Java library does).

pinoyyid
  • 21,499
  • 14
  • 64
  • 115
  • I have posted the code to authenticate. Please take a look. I basically used the code in startup guide taken from Google's website. – jason Mar 10 '17 at 18:01
  • Unfortunately I have never used (nor will ever) the Google python library so I can't help with a code read through. I suggest that immediately before using the credential, you confirm that the Refresh Token looks valid. Also try my item 3, ie. remove permission and try again. – pinoyyid Mar 10 '17 at 19:04