Python - Google OAuth generate token from authorization code

3.2k views Asked by At

I have an python google cloud function which receives a OAuth authorization code as an argument. I want to exchange this code for a token which can be used to authenticate a service object.

The code is generated externally and passed to this function as a string arguement.

I've looked at the documentation for google_auth_oauthlib.flow. But it expects a flow object created to handle the auth. In my case I only have the code as the result.

How can I exchange a authorization code as string into a token?

2

There are 2 answers

5
Trey Griffith On BEST ANSWER

You need a few more pieces of information than just the Authorization Code. Google's docs for how to exchange an Authorization Code into an Access Token are here: Exchange authorization code for refresh and access tokens.

Specifically, in addition to the code, you need:

  • client_id: The client ID obtained from the API Console [Credentials page] |(https://console.developers.google.com/apis/credentials).
  • client_secret: The client secret obtained from the API Console Credentials page.
  • grant_type: authorization_code
  • redirect_uri: The redirect URI used in the initial authorization request. If this is for a CLI (or similar) that might be urn:ietf:wg:oauth:2.0:oob (for out-of-band)

Most of these (client_id, client_secret, grant_type) are static, so you can use them as configuration in your cloud function. The redirect_uri could be static if you're sure of the flow that generated the code.

With that information, you should be able to create the Flow object in your linked example and fetch the token.

As an alternative to storing all this configuration in your cloud function, you could use a managed OAuth service like Xkit (where I work), which handles the authorization process and lets you retrieve access tokens from anywhere (including cloud functions) with just an API key.

0
Cerin On

I recently ran into this problem myself, trying to access the AdSense API. It doesn't help that Google's documentation is very sparse, uses Flask for some odd reason, implies you must retrieve an authorization_response and not the actual authorization code, and refers to a few different non-working Python examples, seemingly written for long-since deprecated Python 1.4.

However, based on their examples, and a few blog posts implementing some more recent fixes (but still broken when I tried them), I managed to piece together some working code.

My file utils.py where I define the initialize_service to initialize my connection to the AdSense API:

"""
Auxiliary file for AdSense Management API code samples.
Handles various tasks to do with logging, authentication and initialization.
"""

import os

from apiclient.discovery import build

from oauth2client.client import OAuth2Credentials
from oauth2client.file import Storage
from googleapiclient.http import build_http

import google_auth_oauthlib.flow

MY_DOMAIN = '<your domain here>'

def initialize_service():
    """Builds instance of service from discovery data and does auth."""

    client_secrets = os.path.join(os.path.dirname(__file__), 'client_secrets.json')

    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        client_secrets, scopes=['https://www.googleapis.com/auth/adsense.readonly'])
    flow.redirect_uri = f'https://{MY_DOMAIN}/oauth2callback'

    # If the credentials don't exist or are invalid run through the native client
    # flow. The Storage object will ensure that if successful the good
    # Credentials will get written back to a file.
    storage = Storage('adsense.dat')
    credentials = storage.get()
    if credentials is None or credentials.invalid:

        auth_url, _ = flow.authorization_url(prompt='consent')
        print('Log into the Google Account you use to access your AdWords account ' \
         'and go to the following URL: \n%s\n' % auth_url)
        print('After approving the token enter the verification code (if specified).')
        code = input('Code:').strip()

        flow.fetch_token(code=code)
        print('Access token: %s' % flow.credentials.token)
        print('Refresh token: %s' % flow.credentials.refresh_token)

        # Flow creates a google.oauth2.credentials.Credentials instance but storage
        # can only save and load a oauth2client.client.Credentials
        # instance, so we have to convert it.
        old_creds = flow.credentials
        good_creds = OAuth2Credentials(
            access_token=old_creds.token,
            client_id=old_creds.client_id,
            client_secret=old_creds.client_secret,
            refresh_token=old_creds.refresh_token,
            token_expiry=old_creds.expiry,
            token_uri=old_creds.token_uri,
            user_agent='my-agent',
            id_token=old_creds.id_token,
            scopes=old_creds.scopes,
        )
        storage.put(good_creds)
        credentials = storage.get()

    http = credentials.authorize(http=build_http())

    service = build("adsense", "v1.4", http=http)

    return service

This is the code that should answer your question, because I get the authorization code, and call flow.fetch_token(code=code) to convert this to a token, which I then store for future re-use in the file adsense.dat.

The problem I ran into was that there are multiple classes from multiple packages for storing OAuth credentials, and they're all confusingly named the same thing, yet they're slightly incompatible. The flow.fetch_token() function stores its credentials internally as a google.oauth2.credentials.Credentials instance, yet the code for storing and loading these credentials only accepts an oauth2client.client.Credentials instance, so I had to write some code to convert from one to the other.

Then, to call the API, you'd have code like:

from utils import initialize_service

service = initialize_service()

result = service.reports().generate(
    startDate='<start date>',
    endDate='<end date>',
    filter=['AD_CLIENT_ID==<your ad client id>'],
    metric=[
        'PAGE_VIEWS',
        'EARNINGS'
    ],
    dimension=[
        'AD_UNIT_NAME',
    ],
).execute()