Google OAuth token request returns "invalid_client": "Unauthorized"

Jaza picture Jaza · Jan 1, 2018 · Viewed 13.3k times · Source

Trying to get OAuth2 Google login working, this is the raw request that my app makes:

Method: POST

URL: https://www.googleapis.com/oauth2/v3/token

Headers: Content-Type: application/x-www-form-urlencoded

Values:

client_id: XXX-0123456789abcdef0123456789abcdef.apps.googleusercontent.com

client_secret: A1b2C3d4E5f6G7h8I9j0K1l2M

code: 1/A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v

grant_type: authorization_code

redirect_uri: http://localhost:5000/callback/google/

And this is the response:

Status: 401 Unauthorized

Body:

{
    "error": "invalid_client",
    "error_description": "Unauthorized"
}

Have verified that this is the exact request / response that my app is making (a Python app using Flask and rauth), and have verified that I can reproduce the exact same request / response using Postman.

Per instructions in other threads, I have done all of the following in the Google APIs console:

  • In "OAuth consent screen" settings, set "Product name" to something different than "Project name"
  • Also in "OAuth consent screen" settings, double-check that email is set
  • Enable the Google+ API
  • Enable the Gmail API
  • Recreate the client ID / secret
  • Double-check that there are no leading or trailing spaces in the client ID / secret values, I have copied them correctly from the API console

No matter what I do, still getting the same response of "invalid_client": "Unauthorized".

Help with this would be appreciated. Am trying to set up OAuth2-powered "Log in with X" functionality in my app, have gotten Facebook and Twitter working without issues, would like to get Google working too, but if I can't resolve this then I'm afraid I'll have to ditch Google auth.

Answer

DaImTo picture DaImTo · Jan 2, 2018

Invalid client means that the client id or the client secret that you are using are not valid. They must be the ones you have downloaded from Google Developer console.

Tip: You might want to consider using the Google python client library it does all the heavy lifting for you.

from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly']

def main():
    """Shows basic usage of the Drive v3 API.
    Prints the names and ids of the first 10 files the user has access to.
    """
    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    service = build('drive', 'v3', credentials=creds)

    # Call the Drive v3 API
    results = service.files().list(
        pageSize=10, fields="nextPageToken, files(id, name)").execute()
    items = results.get('files', [])

    if not items:
        print('No files found.')
    else:
        print('Files:')
        for item in items:
            print(u'{0} ({1})'.format(item['name'], item['id']))

if __name__ == '__main__':
    main()