1

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?

A Clockwork Orange
  • 23,913
  • 7
  • 25
  • 28

2 Answers2

0

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.

Trey Griffith
  • 499
  • 4
  • 8
  • Thanks Trey, this makes sense. I can create a flow object using the client secrets (which I have) but pass the externally generated auth code into fetch_token. – A Clockwork Orange Oct 20 '20 at 17:21
  • 2
    Is there a way to do this without using flask? – Kiran Jan 12 '21 at 11:04
  • @Kiran nothing about this (except the Google library) is specific to python. Google publishes a bunch of libraries for different languages, all of which follow the same flow. [Xkit](https://xkit.co) also supports basically every language. – Trey Griffith Jan 12 '21 at 22:22
  • 1
    @TreyGriffith That doesn't answer the question. Kiran is looking for a way in Python to fetch the token that doesn't use Flask. Essentially answering "yeah probably lots of languages use the API" is unhelpful. Google's docs are horribly vague and poorly written and really don't make it clear how to actually fetch the token. – Cerin Apr 21 '21 at 03:32
  • @Cerin It literally answers Kiran's question (the answer is "yes"). The examples in the linked documentation specifically include Python (which Kiran didn't specify they wanted, only "without using flask"). If Kiran has a more detailed question, a StackOverflow question (rather than a comment on another answer) would be the best place for that. – Trey Griffith Apr 22 '21 at 05:26
0

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()
Cerin
  • 60,957
  • 96
  • 316
  • 522