pygsheets icon indicating copy to clipboard operation
pygsheets copied to clipboard

Issues with Maintaining Authentication

Open rstone13 opened this issue 3 years ago • 9 comments

I have an application that's periodically ran via a cron job in order to read and write data to some Google Sheets. The application works fine for a little over a week or two at a time, then it will crash with this stack trace:

Traceback (most recent call last):
  File "main.py", line 137, in <module>
    remove_old_jobs(args.delete[0])
  File "main.py", line 26, in remove_old_jobs
    master_sheet = MasterGoogleSheet(master_sheet_id)
  File "/home/ec2-user/python/zapier-social-media/misc_utils/master_google_sheet.py", line 14, in __init__
    google_sheets_access = pygsheets.authorize()
  File "/home/ec2-user/python/zapier-social-media/venv/lib/python3.6/site-packages/pygsheets/authorization.py", line 129, in authorize
    credentials = _get_user_authentication_credentials(client_secret, scopes, credentials_directory, local)
  File "/home/ec2-user/python/zapier-social-media/venv/lib/python3.6/site-packages/pygsheets/authorization.py", line 40, in _get_user_authentication_credentials
    credentials.refresh(Request())
  File "/home/ec2-user/python/zapier-social-media/venv/lib/python3.6/site-packages/google/oauth2/credentials.py", line 214, in refresh
    scopes,
  File "/home/ec2-user/python/zapier-social-media/venv/lib/python3.6/site-packages/google/oauth2/_client.py", line 248, in refresh_grant
    response_data = _token_endpoint_request(request, token_uri, body)
  File "/home/ec2-user/python/zapier-social-media/venv/lib/python3.6/site-packages/google/oauth2/_client.py", line 124, in _token_endpoint_request
    _handle_error_response(response_body)
  File "/home/ec2-user/python/zapier-social-media/venv/lib/python3.6/site-packages/google/oauth2/_client.py", line 60, in _handle_error_response
    raise exceptions.RefreshError(error_details, response_body)
google.auth.exceptions.RefreshError: ('invalid_grant: Token has been expired or revoked.', '{\n  "error": "invalid_grant",\n  "error_description": "Token has been expired or revoked."\n}')

According to this Stack Overflow post, this may occur when an application asks for the Refresh Token more times than necessary, leading to the stored ones being expired. This may be occurring in the pygsheets.authorize() method. However, I don't know that this is necessarily the issue (which is why I was hesitant to mark this as a bug).

Is there a way I can keep from having to occasionally go back through the authentication process? I was thinking about redesigning the application so that it just runs in the background. That way, the authorize function only needs to be called once (when the application is launched) instead of needing to be called everytime the application is ran. However, I'd rather not have to do that unless necessary.

rstone13 avatar Mar 12 '21 16:03 rstone13

how many times (approx) does the script runs before it fails auth?

nithinmurali avatar Mar 13 '21 14:03 nithinmurali

I'd say a little over 200 times. However, looking back at my code, it calls the authorize function a few times throughout each run, cause I created an object to encapsulate Sheets and the specific operations I want to perform on each of them and the init function of those objects call the authorize function to get a Client object in order to get the Spreadsheet object. A better way would be to pass the Client object into the init function and only get one Client at main. Even still, that'll only extend the length of time between manual re-authorizations.

rstone13 avatar Mar 13 '21 21:03 rstone13

any solutions to this? i have the exact same issue @rstone13 @nithinmurali

wolfracer avatar Mar 29 '21 16:03 wolfracer

Using Authlib instead of google-auth might help resolve this. (using AssertionSession) This might need some changes in library code. ref

nithinmurali avatar Apr 20 '21 19:04 nithinmurali

This is preventing me from accessing my sheets. From what I have read it is associated with the Oauth shift from 1.29 to 1.30. Are code changes required in pygsheets to accommodate this?

I too am getting this error: google.auth.exceptions.RefreshError: ('invalid_grant: Bad Request', '{\n "error": "invalid_grant",\n "error_description": "Bad Request"\n}')

Please advise! :(

xelcho avatar May 14 '21 16:05 xelcho

There are no code changes for updating the library since there is no version pinning. I am unable to reproduce this, you can try deleting your existing credentials and try again.

As for auto auth refreshing, you can try using this snippet which uses authlib

import json
from authlib.integrations.requests_client import AssertionSession

def create_assertion_session(conf_file, scopes, subject=None):
    with open(conf_file, 'r') as f:
        conf = json.load(f)

    token_url = conf['token_uri']
    issuer = conf['client_email']
    key = conf['private_key']
    key_id = conf.get('private_key_id')

    header = {'alg': 'RS256'}
    if key_id:
        header['kid'] = key_id

    # Google puts scope in payload
    claims = {'scope': ' '.join(scopes)}
    return AssertionSession(
        grant_type=AssertionSession.JWT_BEARER_GRANT_TYPE,
        token_url=token_url,
        issuer=issuer,
        audience=token_url,
        claims=claims,
        subject=subject,
        key=key,
        header=header,
    )

scopes = [
    'https://www.googleapis.com/auth/spreadsheets',
    'https://www.googleapis.com/auth/drive',
]
session = create_assertion_session('your-google-conf.json', scopes)
gc = Client(None, http=session)

nithinmurali avatar May 15 '21 11:05 nithinmurali

Huh! Yeah I tried replacing creds, no luck. I then started from scratch with new gcp project and hit the same issue, that was unexpected. Here is the response:

Traceback (most recent call last): File "C:/Users/test/gsheets_pull_r0.py", line 34, in gc = pygsheets.authorize() File "C:\test\anaconda3\lib\site-packages\pygsheets\authorization.py", line 129, in authorize credentials = _get_user_authentication_credentials(client_secret, scopes, credentials_directory, local) File "C:\test\anaconda3\lib\site-packages\pygsheets\authorization.py", line 40, in _get_user_authentication_credentials credentials.refresh(Request()) File "C:\test\anaconda3\lib\site-packages\google\oauth2\credentials.py", line 228, in refresh ) = reauth.refresh_grant( File "C:\test\anaconda3\lib\site-packages\google\oauth2\reauth.py", line 338, in refresh_grant _client._handle_error_response(response_data) File "C:\test\anaconda3\lib\site-packages\google\oauth2_client.py", line 60, in _handle_error_response raise exceptions.RefreshError(error_details, response_data) google.auth.exceptions.RefreshError: ('invalid_client: Unauthorized', {'error': 'invalid_client', 'error_description': 'Unauthorized'})

xelcho avatar May 15 '21 12:05 xelcho

you are facing this issue when the script if running for long time right? you can access sheets initially but after some time when the token expires, this error happens?

nithinmurali avatar May 15 '21 12:05 nithinmurali

I ran into the same issue. For me, deleting the local sheets.googleapis.com-python.json file, running my script again, and reauthenticating through the printed URL fixed things.

kburns avatar Dec 15 '21 18:12 kburns