4

I'm working on a project in which, in accordance with https://12factor.net/config, we don't things like credentials in our code, but rather in environment variables.

I'm looking into using the Google Sheets API to collate some data from our database and put it into a Google sheet. Here is the partial example script from https://developers.google.com/sheets/api/quickstart/python:

from __future__ import print_function
from apiclient.discovery import build
from httplib2 import Http
from oauth2client import file as oauth_file, client, tools

# Setup the Sheets API
SCOPES = 'https://www.googleapis.com/auth/spreadsheets.readonly'
store = oauth_file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
    flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
    creds = tools.run_flow(flow, store)
service = build('sheets', 'v4', http=creds.authorize(Http()))

Firstly, it is not clear to me from the documentation what 'token.json' and 'credentials.json' should be in this example. From the API console, in the Credentials tab, I downloaded a client_secret_<long suffix>.json which looks like this:

{"installed":{"client_id":"[our_client_id]","project_id":"nps-survey-1532981793379","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"[our_client_secret]","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}}

Should this JSON file be the 'token.json' in this example, or the 'credentials.json'? Also, is there a way to instantiate valid creds by specifying the client secret and client ID directly, and not using this JSON file?

tehhowch
  • 9,645
  • 4
  • 24
  • 42
Kurt Peek
  • 52,165
  • 91
  • 301
  • 526
  • 1
    You can call the constructor of `Credentials` directly with the relevant values. Note that the role of `token.json` is to maintain the access token and related information between script executions. It isn't required. – tehhowch Jul 30 '18 at 22:20
  • 1
    Don't think that you can get rid of credentials.json file as it's where your creds are stored. Also, it's mentioned in [Using OAuth 2.0 for Web Server Applications](https://developers.google.com/api-client-library/python/auth/web-app) that "Do not store the client_secret.json file in a publicly-accessible location. In addition, if you share the source code to your application—for example, on GitHub—store the client_secret.json file outside of your source tree to avoid inadvertently sharing your client credentials." so it's implied that you'll always need that file. – ReyAnthonyRenacia Jul 31 '18 at 12:24
  • Unfortunately, the platform we deploy on, Aptible, is 'built' from a Dockerfile and environment variables; there is no way to add a file to the container without checking it into version control. (The deployment is trigger upon git push). I'm currently looking into a somewhat hacky way in which I strip out the keys from the JSON file and 'rebuild' it from environment variables. – Kurt Peek Jul 31 '18 at 16:15
  • In some CI program environments, you are able to configure the container initialization from a web interface, in which you supply environment variables securely. Perhaps such an approach is available in Aptible? I would imagine their customer service should be able to assist you – tehhowch Jul 31 '18 at 17:18

1 Answers1

5

I ended up going through the OAuth 2.0 setup for a web application instead of an installed application, and using google_auth_oauthlib. The Flow object has a class method from_client_config() which can be used like so (cf. https://developers.google.com/identity/protocols/OAuth2WebServer):

from django.conf import settings
from django.shortcuts import redirect
import google.oauth2.credentials
import google_auth_oauthlib.flow


# Client configuration for an OAuth 2.0 web server application
# (cf. https://developers.google.com/identity/protocols/OAuth2WebServer)
CLIENT_CONFIG = {'web': {
    'client_id': settings.GOOGLE_CLIENT_ID,
    'project_id': settings.GOOGLE_PROJECT_ID,
    'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
    'token_uri': 'https://www.googleapis.com/oauth2/v3/token',
    'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',
    'client_secret': settings.GOOGLE_CLIENT_SECRET,
    'redirect_uris': settings.GOOGLE_REDIRECT_URIS,
    'javascript_origins': settings.GOOGLE_JAVASCRIPT_ORIGINS}}

# This scope will allow the application to manage your calendars
SCOPES = ['https://www.googleapis.com/auth/calendar']



def get_authorization_url():
    # Use the information in the client_secret.json to identify
    # the application requesting authorization.
    flow = google_auth_oauthlib.flow.Flow.from_client_config(
        client_config=CLIENT_CONFIG,
        scopes=SCOPES)

    # Indicate where the API server will redirect the user after the user completes
    # the authorization flow. The redirect URI is required.
    flow.redirect_uri = 'http://localhost:8000'

    # Generate URL for request to Google's OAuth 2.0 server.
    # Use kwargs to set optional request parameters.
    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='true')

    return authorization_url, state

The settings attributes are, in turn, generated by calling os.getenv() for each corresponding attribute. In this way, the configuration can be obtained from environment variables instead of a local file.

Kurt Peek
  • 52,165
  • 91
  • 301
  • 526
  • I downloaded a config file from the API console that provides the data you have here in CLIENT_CONFIG. However, when I try to run `flow.fetch_token()` I'm getting `ValueError: Please supply either code or authorization_response parameters.` Any idea what it's trying to tell me? – sixty4bit Aug 24 '20 at 19:21
  • Please note that if you are keeping your client secret and others in git (I assume django config is in git repo), it really is like keeping your whole client config in git. – Marcin Sep 28 '22 at 23:06