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.

- 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 - The user grants us access by logging into their Google account and saying “yes, I grant this app consent.”
- 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
) - 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 - Our app takes that payload and extracts an authorization code
- 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
) - 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:
- Create your GCP (Google Cloud Platform) Project
- Enable the Google Calendar API
- Set up OAuth credentials to identify your app to Google (and an OAuth consent screen)
- 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.

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.

Select NEW PROJECT and name your project.

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

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

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

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.

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.

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

Set up OAuth credentials and consent screen
Go to the Credentials page and select CREATE CREDENTIALS in the top left > OAuth client ID.

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.

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.

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.

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.

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) |
Otherwise, continue below to read step by step instructions.
- Install the packages
- Set up an authorization route that users hit (users grant access to your app here e.g.
/authorize-my-app
) - Set up the redirect route (this is related to the redirect URI you set up in GCP) to receive the authorization code from Google
- Retrieve the authorization code and fetch the access token
- 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.
- Python installed (2.6+)
- Python package manager pip
- Google API Client Library in Python
- Google Auth Libraries (google-auth, google-auth-oauthlib, google-auth-httplib2)
- flask (Python web application framework)
- requests (work with HTTP requests in Python)
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.

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.

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
- the path to the file (it’s the filename if it’s in your root directory)
- 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') |
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) |

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 ^_^.

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) |

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.

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.
- grab the URL in the redirect response which contains our access code (which we later need to exchange for an access token)
- exchange the access code we receive in the redirect response for an access token
- 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 |
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 |
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 | |
} |
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) |
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).