How to authorize requests to Google Calendar API

Though I use Python here, the flow should generally be the same across all languages and APIs. You don’t have to be a Python or flask expert to understand this post as it’s more about the OAuth2 lifecycle with Google APIs but, I recommend having the docs open on flask and Python to understand some of the nuances of the helper methods and objects used.

We’ll cover three parts to get authorization to work.

  • Understand the 3-Legged OAuth Flow
  • Understand the non-coding side of things (setting up GCP projects, OAuth consent screens, OAuth client ID)
  • Understand the code side of things (redirect URLs, authorization URLs, payloads)

The 3-Legged OAuth Flow

The 3-Legged OAuth flow refers to the authorization flow to get what is known as an access token which allows us to do things on behalf of a user. In our case, that is someones Google Calendar.

oauth authorization diagram
Ah yes, my beautiful drawing skills
  1. We direct user to an endpoint e.g /authorize to show what is known as an OAuth consent screen where user grants us access to various scopes
  2. The user grants us access by logging into their Google account and saying “yes, I grant this app consent.”
  3. Our app will then take that users consent to what is known as an authorization server on Google’s side (e.g. example URL that doesn’t exist: https://auth.google.com/authorization)
  4. Google will then take that user authorization then send what is known as an authorization code back to our app to a redirect URL that we define (e.g. http://localhost:8080/my-redirect-uri) in a payload
  5. Our app takes that payload and extracts an authorization code
  6. We then take that authorization code and send it back to Google to receive what is known as an access token (e.g. another example URL that doesn’t exist: https://auth.google.com/trade-authorization-code-for-access-token)
  7. Our app extracts the access token from the payload that is sent back to the same redirect URI

Steps within GCP (no code)

The steps for this are the following:

  1. Create your GCP (Google Cloud Platform) Project
  2. Enable the Google Calendar API
  3. Set up OAuth credentials to identify your app to Google (and an OAuth consent screen)
  4. Set up a redirect URI (this is where Google sends your authorization code to exchange for an access code that you use to authenticate requests)

Create your GCP project

You’ll want to visit the Google API Console (found at: https://console.cloud.google.com/apis/dashboard) and you’ll see something similar to the image below.

The Google API Dashboard after creating a project

If you look at the top left you’ll see a drop down next to “Google Cloud Platform”, upon clicking that drop down you’ll be prompted to either select a project or use an existing project. For this exercise, we’ll create a new project.

You can select an existing project or create a new one

Select NEW PROJECT and name your project.

You should see something similar, you can usually just default to No organization for location or select one if you have one.

Once you click CREATE you will be brought back to your dashboard and in the top left you’ll see your project information.

Your dashboard should show something similar

Enable the Google Calendar API

Select the hamburger menu in the top left and go to APIs & Services and select Dashboard.

Select Dashboard

You should be in a dashboard similar to the one below.

The APIs & Services dashboard view

You’re going to click the ENABLE APIS AND SERVICES in the top left and it’ll take you to a library of Google APIs.

The Google API library

Once there, you’re going to type in “Google Calendar” and select Google Calendar API.

Click Enable which enables the Google Calendar API for your specific GCP project.

Enable is what you want here

If everything was done correctly you should now be in a dashboard of sorts for the Google Calendar API.

Google Calendar API dashboard

Go to the Credentials page and select CREATE CREDENTIALS in the top left > OAuth client ID.

Select the OAuth ClientID option

If this is your first time setting up OAuth for this particular GCP project you will be prompted to create an OAuth consent screen. For our case, that’s a yes and you should see a screen similar to the one below asking you to configure an OAuth consent screen.

For any app that uses the 3-legged OAuth flow you have to have a consent screen

Click CONFIGURE CONSENT SCREEN and for now select External.

Fill out your app information and move onto the next page Scopes. For our case, we want all of the Google Calendar API scopes so you’ll want to select /auth/calendar as shown below.

This grants us all permissions

Save and continue. In the Test Users section make sure you add yourself or the emails of the users you are testing your app with. If you don’t your app will not work so make sure you do this step correctly. After completing that you’ll end up on the Summary section and you should now be able to add OAuth credentials without being asked to create a consent screen.

Select Credentials and Web application as your application type, scrolling down you’ll see a prompt for a redirect URI. This URI will be where the access token is sent to after a user grants consent to your app. The URI you enter here will be the endpoint you create in your app which Google will send a payload to. For testing purposes you can set it as localhost like I do below.

Try to set it as something easy to remember in development

Upon completion you’ll see a prompt that shows your OAuth credentials and a downloadable JSON file. These are your credentials be sure to keep them safe.

You should see your own Client ID and Client Secret which identifies your application

That was a lot but that’s it for all the GCP steps! You have successfully created a GCP project, OAuth Consent screen and OAuth credentials to match. With these set up we can now write some code and get your app authenticated and authorized to send requests to the Google Calendar API and modify resources.

Steps outside of GCP (code)

If you just want the code, find my gist below and run the code after installing the requirements with python3 app.py (be sure to paste your credentials into a corresponding client_secrets.json file within the same root directory.

import flask
import google.oauth2.credentials
import google_auth_oauthlib.flow
import os
from flask import Flask
from flask import redirect
from flask import request
from googleapiclient.discovery import build
# set up a Flow object that reads the clients from our secrets file with the
# corresponding scope
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
'client_secrets.json',
scopes=['https://www.googleapis.com/auth/calendar'])
# indicate the redirect URI that we placed in the console redirect URI when we
# created the oauth credentials
flow.redirect_uri = 'http://localhost:8080/oauth2redirect'
# generates the auth URL that we need to redirect users to where the user
# gets the oauth consent screen and we get the access code to later exchange for an
# auth token
authorization_url, _ = flow.authorization_url(
# enables us to grab a refresh token without the user granting us access
# a second time if needed
access_type='offline',
include_granted_scopes='true')
# create our Flask web app
app = Flask(__name__)
# this allows transport over HTTP for development purposes, if excluded
# HTTPS is needed
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
@app.route("/test-api-request")
def test_api_request():
"""Tests an API request to Google Calendar."""
# grab the credentials from our flask session, in production
# you will probably store this in some persistent database per user
credentials = google.oauth2.credentials.Credentials(
**flask.session['credentials'])
# build the Google Calendar service which we use to represent the Google Calendar
# API
gcal = build('calendar', 'v3', credentials=credentials)
# grabs Google Calendar events for the particular user who authorized the app
events_result = gcal.events().list(calendarId='primary',
maxResults=10, singleEvents=True).execute()
# return a JSON response to the front end that shows the results
return {
"msg": "successfully processed request",
"data": events_result
}
@app.route("/authorize-user")
def auth_user():
"""
Redirects a user to Google's authorization server to show the OAuth
Consent screen and get user consent.
"""
return redirect(authorization_url)
@app.route("/oauth2redirect")
def oauth2_redirect():
"""
The redirect URI that Google hits after user grants access in the OAuth
consent screen where we fetch the access token from the access code given in the
URL and set them in the flask session.
"""
# grabs the URL response from the redirect after auth
authorization_response = request.url
# fetchs the access code from the request url response
# and then exchanges it for the token
flow.fetch_token(authorization_response=authorization_response)
# grab and set credentials into your flask session
# TODO: in production move these credentials to a persistent data store.
credentials = flow.credentials
flask.session['credentials'] = {
'token': credentials.token,
'refresh_token': credentials.refresh_token,
'token_uri': credentials.token_uri,
'client_id': credentials.client_id,
'client_secret': credentials.client_secret,
'scopes': credentials.scopes}
return flask.redirect(flask.url_for('test_api_request'))
if __name__ == "__main__":
app.secret_key = "development"
app.run(port=8080, debug=True)
view raw app.py hosted with ❤ by GitHub

Otherwise, continue below to read step by step instructions.

  1. Install the packages
  2. Set up an authorization route that users hit (users grant access to your app here e.g. /authorize-my-app)
  3. Set up the redirect route (this is related to the redirect URI you set up in GCP) to receive the authorization code from Google
  4. Retrieve the authorization code and fetch the access token
  5. Make requests to the Google Calendar API

Install the packages

Thanking our lucky stars smart people have implemented so many nice things we can install the below to get up and running relatively quickly. I recommend that you set up your own virtual environment and install packages there.

Once pip is installed and you are in your virtual environment (if you are using it) you can run the below command to get everything installed. Make sure to do this in a new project directory.

pip install --upgrade google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client flask requests

Write the authorization route (user facing)

Google has this nice thing called a Flow (Sourcegraph is an awesome code search tool, check out the object if you’d like in the link before) object in the google-auth-oauthlib.flow module. We’ll use our client credentials JSON file from when we created our OAuth credentials. If you didn’t download the credentials you can easily re-download them in the Credentials page under the Actions button. Once you download those credentials move them to the root of your project.

To the far right in Actions is where you can re-download the OAuth credentials

If you open the JSON file you’ll see a set of credentials that looks something like the below where your credentials are unique to you.

This is the information that is unique to your application

We’ll use the method from_client_secrets_file to construct a new Flow instance from our JSON credentials file. We need to pass two parameters

  1. the path to the file (it’s the filename if it’s in your root directory)
  2. the Google Calendar API scopes that we want (for us that’s https://www.googleapis.com/auth/calendar which grants all access)

Once we create a Flow instance we’ll be able to set the redirect URI and grab the authorization URL which is the URL that shows the OAuth consent screen to users. The code will look something like the below.

# set up a Flow object that reads the clients from our secrets file with the
# corresponding scope
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
'client_secrets.json',
scopes=['https://www.googleapis.com/auth/calendar'])
# indicate the redirect URI that we placed in the console redirect URI when we
# created the oauth credentials
flow.redirect_uri = 'http://localhost:8080/oauth2redirect'
# generates the auth URL that we need to redirect users to where the user
# gets the oauth consent screen and we get the access code to later exchange for an
# auth token
authorization_url, _ = flow.authorization_url(
# enables us to grab a refresh token without the user granting us access
# a second time if needed
access_type='offline',
include_granted_scopes='true')
view raw flow.py hosted with ❤ by GitHub

With the above code we can now construct our authorization route which will redirect a user to Google’s authorization server which shows the OAuth consent screen to the user. This route is a simple redirect to the Google Auth Server.

# create our Flask web app
app = Flask(__name__)
# this allows transport over HTTP for development purposes, if excluded
# HTTPS is needed
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
@app.route("/authorize-user")
def auth_user():
"""
Redirects a user to Google's authorization server to show the OAuth
Consent screen and get user consent.
"""
return redirect(authorization_url)
view raw auth.py hosted with ❤ by GitHub
It’ll ask you to login then after confirm scopes

In the login screen, select the user that you added as a test user when we configured the OAuth project within your GCP console. Select “Continue” when you see the “Unsafe” warning, as you are the developer and you can trust yourself ^_^.

You’ll select “Continue” here and accept the defined scopes which we defined as all R/W access

When you click Continue you should hit a 404 error in your browser like the below with the code shown so far. This is because after you authorized the app in the OAuth Consent Screen Google attempted to make a request to your redirect URI (which we haven’t written yet).

import flask
import google.oauth2.credentials
import google_auth_oauthlib.flow
import os
from flask import Flask
from flask import redirect
from flask import request
from googleapiclient.discovery import build
# set up a Flow object that reads the clients from our secrets file with the
# corresponding scope
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
'client_secrets.json',
scopes=['https://www.googleapis.com/auth/calendar'])
# indicate the redirect URI that we placed in the console redirect URI when we
# created the oauth credentials
flow.redirect_uri = 'http://localhost:8080/oauth2redirect'
# generates the auth URL that we need to redirect users to where the user
# gets the oauth consent screen and we get the access code to later exchange for an
# auth token
authorization_url, _ = flow.authorization_url(
# enables us to grab a refresh token without the user granting us access
# a second time if needed
access_type='offline',
include_granted_scopes='true')
# create our Flask web app
app = Flask(__name__)
# this allows transport over HTTP for development purposes, if excluded
# HTTPS is needed
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
@app.route("/authorize-user")
def auth_user():
"""
Redirects a user to Google's authorization server to show the OAuth
Consent screen and get user consent.
"""
return redirect(authorization_url)
view raw auth.py hosted with ❤ by GitHub
This is Google attempting to find your non-existent redirect URI

If you take a look at your terminal you should see a 302 redirect and a corresponding GET request to your redirect URI which resolves to a 404. If you read the attempted redirect URL carefully you’ll see the access code and other parameters buried in there. We need to retrieve that to exchange it for an access token.

The access code is embedded in the redirect response URL

Write the redirect route (Google facing)

To get rid of the 404 we’ll have to write the route that Google is looking to redirect to. In our case, that is http://localhost:8080/oauth2redirect which we defined when creating our OAuth credentials in the GCP console.

There’s a few parts here to complete the redirect route completely.

  1. grab the URL in the redirect response which contains our access code (which we later need to exchange for an access token)
  2. exchange the access code we receive in the redirect response for an access token
  3. set the credentials (for this tutorial we set it directly in the flask session but in production you’ll want to save those to a persistent store somewhere)

With flask we can complete step 1 with the below.

@app.route("/oauth2redirect")
def oauth2_redirect():
"""
The redirect URI that Google hits after user grants access in the OAuth
consent screen where we fetch the access token from the access code given in the
URL and set them in the flask session.
"""
# grabs the URL response from the redirect after auth
authorization_response = request.url
return "OK" # placeholder
view raw redirect.py hosted with ❤ by GitHub

Retrieving the access token

For steps 2 and 3, we can utilize the fetch_token method and pass in the URL to grab the token then store those credentials into our flask session object. In all, it looks like this.

@app.route("/authorize-user")
def auth_user():
"""
Redirects a user to Google's authorization server to show the OAuth
Consent screen and get user consent.
"""
return redirect(authorization_url)
@app.route("/oauth2redirect")
def oauth2_redirect():
"""
The redirect URI that Google hits after user grants access in the OAuth
consent screen where we fetch the access token from the access code given in the
URL and set them in the flask session.
"""
# grabs the URL response from the redirect after auth
authorization_response = request.url
# fetchs the access code from the request url response
# and then exchanges it for the token
flow.fetch_token(authorization_response=authorization_response)
# grab and set credentials into your flask session
# TODO: in production move these credentials to a persistent data store.
credentials = flow.credentials
flask.session['credentials'] = {
'token': credentials.token,
'refresh_token': credentials.refresh_token,
'token_uri': credentials.token_uri,
'client_id': credentials.client_id,
'client_secret': credentials.client_secret,
'scopes': credentials.scopes}
return flask.redirect(flask.url_for('test_api_request')) # to be written
view raw redirect.py hosted with ❤ by GitHub

Make requests to the Google Calendar API

I’ve written code to test an API request below. What remains is grabbing the credentials from the flask session object we put in earlier and passing that to Google’s build service. After that you should have a successful response in your browser.

@app.route("/test-api-request")
def test_api_request():
"""Tests an API request to Google Calendar."""
# grab the credentials from our flask session, in production
# you will probably store this in some persistent database per user
credentials = google.oauth2.credentials.Credentials(
**flask.session['credentials'])
# build the Google Calendar service which we use to represent the Google Calendar
# API
gcal = build('calendar', 'v3', credentials=credentials)
# grabs Google Calendar events for the particular user who authorized the app
events_result = gcal.events().list(calendarId='primary',
maxResults=10, singleEvents=True).execute()
# return a JSON response to the front end that shows the results
return {
"msg": "successfully processed request",
"data": events_result
}
view raw test.py hosted with ❤ by GitHub

All in all, your code should look like the below (which is the same as the first block you saw above).

import flask
import google.oauth2.credentials
import google_auth_oauthlib.flow
import os
from flask import Flask
from flask import redirect
from flask import request
from googleapiclient.discovery import build
# set up a Flow object that reads the clients from our secrets file with the
# corresponding scope
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
'client_secrets.json',
scopes=['https://www.googleapis.com/auth/calendar'])
# indicate the redirect URI that we placed in the console redirect URI when we
# created the oauth credentials
flow.redirect_uri = 'http://localhost:8080/oauth2redirect'
# generates the auth URL that we need to redirect users to where the user
# gets the oauth consent screen and we get the access code to later exchange for an
# auth token
authorization_url, _ = flow.authorization_url(
# enables us to grab a refresh token without the user granting us access
# a second time if needed
access_type='offline',
include_granted_scopes='true')
# create our Flask web app
app = Flask(__name__)
# this allows transport over HTTP for development purposes, if excluded
# HTTPS is needed
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
@app.route("/test-api-request")
def test_api_request():
"""Tests an API request to Google Calendar."""
# grab the credentials from our flask session, in production
# you will probably store this in some persistent database per user
credentials = google.oauth2.credentials.Credentials(
**flask.session['credentials'])
# build the Google Calendar service which we use to represent the Google Calendar
# API
gcal = build('calendar', 'v3', credentials=credentials)
# grabs Google Calendar events for the particular user who authorized the app
events_result = gcal.events().list(calendarId='primary',
maxResults=10, singleEvents=True).execute()
# return a JSON response to the front end that shows the results
return {
"msg": "successfully processed request",
"data": events_result
}
@app.route("/authorize-user")
def auth_user():
"""
Redirects a user to Google's authorization server to show the OAuth
Consent screen and get user consent.
"""
return redirect(authorization_url)
@app.route("/oauth2redirect")
def oauth2_redirect():
"""
The redirect URI that Google hits after user grants access in the OAuth
consent screen where we fetch the access token from the access code given in the
URL and set them in the flask session.
"""
# grabs the URL response from the redirect after auth
authorization_response = request.url
# fetchs the access code from the request url response
# and then exchanges it for the token
flow.fetch_token(authorization_response=authorization_response)
# grab and set credentials into your flask session
# TODO: in production move these credentials to a persistent data store.
credentials = flow.credentials
flask.session['credentials'] = {
'token': credentials.token,
'refresh_token': credentials.refresh_token,
'token_uri': credentials.token_uri,
'client_id': credentials.client_id,
'client_secret': credentials.client_secret,
'scopes': credentials.scopes}
return flask.redirect(flask.url_for('test_api_request'))
if __name__ == "__main__":
app.secret_key = "development"
app.run(port=8080, debug=True)
view raw app.py hosted with ❤ by GitHub

And that’s it! You should be able to hit the Google Calendar API as much as you want now (of course, within the rate limits and bounds of what is legal :P).

Published by Paul Young-Suk Lee

SWE @lyft. Currently working on data infrastructure

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: