简体   繁体   中英

Gmail API error when access token expires (Python, Django): HttpAccessTokenRefreshError invalid_client: The OAuth client was not found

i'm building a Django project. I need to send emails through Gmail's API, so i created a project, got API keys, oauth credentials, etc.

I managed to do the "authorization" part, where the user signs in, gives you permission to read + send emails (with access_type=offline, as far as I know) and you get an access_token. I save the "access_token", the "expires_in" and the "refresh_token" fields for the user in a database.

Then, i can read the labels of the user's emails, send emails in his name, etc. No problem.

The problem arises once the access_token expires (so, after an hour of the user giving permission).

When trying to send an email, the error i get is:

HttpAccessTokenRefreshError at /test_gmail_api3/

invalid_client: The OAuth client was not found.

I browsed a lot of related stackoverflow questions and tried changing a lot of different things but nothing works. The only thing that works is manually revoking access from my test gmail account, giving access again, and then it works perfectly... until the access_token expires again.

This is the error:

HttpAccessTokenRefreshError的屏幕截图

And these are the relevant parts of the code (t ):

This are the relevant parts of the code:

View used to ask user for consent/permission (works well, redirects to test_gmail_api2 with a code:

def test_gmail_api(request):
flow = OAuth2WebServerFlow(client_id='<< edited to keep secret >>.apps.googleusercontent.com',
                       client_secret='<< edited to keep secret >>MpNLDa',
                       scope=('https://www.googleapis.com/auth/gmail.readonly ' + 'https://www.googleapis.com/auth/gmail.send'),
                       redirect_uri='http://localhost:8000/test_gmail_api2/')
auth_uri = flow.step1_get_authorize_url()
return redirect(auth_uri)

view used to use the google code to get access_token and refresh_token (works well, i get the tokens and save them as strings):

def test_gmail_api2(request):
flow = OAuth2WebServerFlow(client_id='<< edited to keep secret >>.apps.googleusercontent.com',
                       client_secret='<< edited to keep secret >>MpNLDa',
                       scope=('https://www.googleapis.com/auth/gmail.readonly ' + 'https://www.googleapis.com/auth/gmail.send'),
                       redirect_uri='http://localhost:8000/test_gmail_api2/')
credentials = flow.step2_exchange(request.GET.get('code', ''))
current_profile = Profile.objects.get(email_address='none@test.com')
current_profile.access_token = credentials.get_access_token(http=None).access_token
current_profile.access_token_expiration = credentials.get_access_token(http=None).expires_in

string_credentials = credentials.to_json()
json_credentials = json.loads(string_credentials)

current_profile.refresh_token = json_credentials['refresh_token']
current_profile.save()

service = build('gmail', 'v1', http=credentials.authorize(Http()))

# Call the Gmail API
test_response = ""
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
if not labels:
    test_response += 'No labels found.'
else:
    test_response += 'Labels:'
    for label in labels:
        test_response += label['name'] + "<br>"

response = service.users().messages().list(userId='me',
                                           q='has:attachment').execute()
messages = []
if 'messages' in response:
  messages.extend(response['messages'])
test_response += string_credentials
return HttpResponse(test_response)

Finally, the view that reads all labels and sends an email:

def test_gmail_api3(request):
client_id='<< edited to keep secret >>.apps.googleusercontent.com',
client_secret='<< edited to keep secret >>MpNLDa'
current_profile = Profile.objects.get(email_address='none@test.com')
refresh_token = current_profile.refresh_token
access_token = current_profile.access_token
expires_at = current_profile.access_token_expiration
some_user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.517 Safari/537.36'
cred = oauth2client.client.GoogleCredentials(access_token,client_id,client_secret,
                                      refresh_token,expires_at,"https://accounts.google.com/o/oauth2/token",some_user_agent) # tried access_token=None and token_expiry=None but it doesn't work
http = cred.authorize(httplib2.Http())
service = build('gmail', 'v1', http=cred.authorize(Http()))

# Call the Gmail API
test_response = ""

string_credentials = cred.to_json()
json_credentials = json.loads(string_credentials)
test_response += string_credentials + '<br>'

results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
if not labels:
    test_response += 'No labels found.'
else:
    test_response += 'Labels:'
    for label in labels:
        test_response += label['name'] + "<br>"

response = service.users().messages().list(userId='me',
                                           q='has:attachment').execute()
messages = []
if 'messages' in response:
  messages.extend(response['messages'])
test_response += cred.to_json() + '<br>'

user_id='me'
threads = service.users().threads().list(userId=user_id).execute().get('threads', [])
for thread in threads:
    tdata = service.users().threads().get(userId=user_id, id=thread['id']).execute()
    nmsgs = len(tdata['messages'])

    if nmsgs > 2:    # skip if <3 msgs in thread
        msg = tdata['messages'][0]['payload']
        subject = ''
        for header in msg['headers']:
            if header['name'] == 'Subject':
                subject = header['value']
                break
        if subject:  # skip if no Subject line
            test_response += subject + ' has num of mssgs: ' + str(nmsgs) + '<br>'

message = MIMEText('blah blah blah')
message['to'] = 'my_personal_email@gmail.com'
message['from'] = 'me'
message['subject'] = 'it seems to be working'
final_mssg =  {'raw': base64.urlsafe_b64encode(message.as_string().encode()).decode()}
try:
    message = (service.users().messages().send(userId='me', body=final_mssg).execute())
    test_response += 'WE JUST SENT A MESSAGE WITH ID ' + message['id']
except Exception as e:
    test_response += "we couldn't send the message. Error: " + str(e)
return HttpResponse(test_response)

I don't know what's the problem. I've tried changing a lot of things in the code, but can't get away from this error. I've been battling with this for 2 days know and i'm going crazy. Can somebody help me please?

Thanks!

PS: this is the database entry where the tokens are correctly saved:

django database entry with token strings

My god, I just found the solution.

I went through the error log again and noticed that the variable "client_id" was a list instead of a string:

local vars in error log

Went back to the code an saw that there was an extra comma were there shouldn't be (hence, making "client_id" a list instead of a string like it should):

extra comma in the code causing the error

And this is what triggered the error. I guess it only triggered it when it needed to refresh the token.

Removed the comma and works nicely now. Silly me. 🤦

Sorry about the question, it was very specific to me and i guess it won't ever help anyone.

I know that the code needs a lot of redoing and tidyng up (for example, i do cred.authorize() 2 times!), it was just for testing purposes and i got stuck in it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM