nithinmurali / pygsheets

Google Sheets Python API v4
https://pygsheets.readthedocs.io/en/latest
Other
1.51k stars 220 forks source link

Token has been expired or revoked #541

Closed Klikovskiy closed 1 year ago

Klikovskiy commented 2 years ago

Hello! How can I disable frequent refresh of the Token? Quite often, you have to renew the token for the errors “Token has been expired or revoked.”

ring0jke commented 2 years ago

To be exact every 2 weeks u have to update the token. If ur app in Test Mode, u cannot bypass this thing. Only in production mode its possible to make a forever token :)

flavianh commented 1 year ago

Actually, there are valid reasons for which a refresh token could be revoked even in production. The documentation from Google lists a few

To me the problem is in this area of the pygsheet code. The line credentials.refresh(Request()) can throw these two errors by example:

google.auth.exceptions.RefreshError: ('invalid_grant: Bad Request', {'error': 'invalid_grant', 'error_description': 'Bad Request'})
google.auth.exceptions.RefreshError: ('deleted_client: The OAuth client was deleted.', {'error': 'deleted_client', 'error_description': 'The OAuth client was deleted.'})

In the meantime, I'm now instantiating pygheets using this piece of code:

from google.auth.exceptions import RefreshError

def gclient():
    try:
        return pygsheets.authorize(client_secret="client_secret.json", local=True)
    except RefreshError:
        credentials_filename = 'sheets.googleapis.com-python.json'
        if os.path.exists(credentials_filename):
            os.remove(credentials_filename)
        return pygsheets.authorize(client_secret="client_secret.json", local=True)

I think that you could fix it for good in pygsheets in the following way:


def get_new_credentials(client_secret_file, scopes, local=False):
    if local:
        flow = InstalledAppFlow.from_client_secrets_file(client_secret_file, scopes)
        return flow.run_local_server()
    else:
        flow = Flow.from_client_secrets_file(client_secret_file, scopes=scopes,
                                                redirect_uri='urn:ietf:wg:oauth:2.0:oob')
        auth_url, _ = flow.authorization_url(prompt='consent')

        print('Please go to this URL and finish the authentication flow: {}'.format(auth_url))
        code = input('Enter the authorization code: ')
        flow.fetch_token(code=code)
        return flow.credentials
def _get_user_authentication_credentials(client_secret_file, scopes, credential_directory=None, local=False):
    credentials = None
    if os.path.exists(credentials_path):
        # expect these to be valid. may expire at some point, but should be refreshed by google api client...
        credentials = Credentials.from_authorized_user_file(credentials_path, scopes=scopes)

    if credentials:
        if credentials.expired and credentials.refresh_token:
            try:
                credentials.refresh(Request())
            except RefreshError:
                os.remove(credentials_path)
                credentials = get_new_credentials(client_secret_file, scopes, local)
    else:
        credentials = get_new_credentials(client_secret_file, scopes, local)

Do you agree? Can I make a PR for this?

nithinmurali commented 1 year ago

@flavianh Sounds good, you can make a PR with this.