In this article, I’ll walk you through the agony of consuming Google Calendar API in Node with non-existent documentation. Particularly, accessing data using service account with domain-wide authority.
If you haven’t been working with Google APIs, or at least not in Node. you might be surprised how incomplete their documentation is. How fussy could it be to integrate a Google API? I will dissect the issues I ran into in latter parts.
Here, in Code Chrysalis, we divide students’ day into atomic unit of Block. Each block will be assigned to at least one instructor to handle the prescribed activities or lectures.
We develop our internal platform, Monarch, to manage students’ schedules, repositories, instructors’ rosters, etc. The back-end is powered by Express.js with GraphQL and TypeORM, written entirely in TypeScript.
Besides Monarch, staffs use G-suite and Google Calendar heavily to reflect their most accurate schedule.
During my first month with Code Chrysalis, I had to manually create events from my roster to reflect my actual schedule on Google Calendar. For a software engineer, this is a pain in the ass. Thus, synchronizing our platform with Google Calendar become my first must-have feature to build.
(left) When an instructor is assigned,.. (right) an event is created automatically.
What I want:
Since we are using GraphQL, adding a hook into my Resolver is all I have to do. Whenever a Block is mutated, the logic to update the corresponding Google Calendar event is then triggered.
The requirements are straightforward, consuming Google APIs is not!
My main challenges were:
Google provides an authentication mechanism for server-to-server application using service account. A service account is an account representing an application instead of an individual user. It allows our application to work with its own data on Google instead of users’ data. However, with domain-wide delegation, we can grant our service account access to members’ data in the organization (provided you are using G-suite). Following this guide, we granted our service account proper privilege to access calendars in the organization
When everything is set (or at least what I think it is), I’d love to validate it. I leaned towards writing code to test if my service account could properly access and mutate our Google Calendar data.
Hence, I dived into the documentation of google-auth-library-nodejs, trying to produce the dumbest code in Node that could read my own calendar, but to no avail.
After several attempts in Node, I decided to try my credentials using Google’s Python library. Using this guide, I managed to read my calendar list with several lines of code within a few minutes
from google.oauth2 import service_account
from googleapiclient.discovery import build
SCOPES = ['https://www.googleapis.com/auth/calendar']
SERVICE_ACCOUNT_FILE = './xxxxxxxxxxxx.json' # You should make it an environment variable
credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject('[email protected]')
service = build('calendar', 'v3', credentials=delegated_credentials)
print(service.calendarList().list().execute())
> Accessing calendar data with delegated credentials in Python
Up to this point, I know my service account is properly set up!
Here comes the problem. My service account works, but I can’t seem to find anything related to delegated credential in google-auth-library-nodejs. To access everyone’s calendar, I needed to set up delegated credentials like how I would do in Python.
The best documentation is sometimes the code itself.
I opened up the source code of Google’s Python client lib, and found the code that enables delegated credentials, which is by setting the subject property. However, I didn’t seem to find any related keyword in the Node client docs. Therefore, I went through the source code of google-auth-library-nodejs and eventually figured out how to create delegated credentials in Node.
const google = require("googleapis").google;
const calendar = google.calendar("v3");
(async function () {
const scopes = ['https://www.googleapis.com/auth/calendar'];
const keyFile = './xxxxxxxxxxx.json'; // Your should make it an environment variable
const client = await google.auth.getClient({
keyFile,
scopes,
});
// Delegated Credential
client.subject = "[email protected]";
const res = await calendar.calendarList.list({
auth: client
});
console.log(JSON.stringify(res.data));
})();
> Accessing calendar list with delegated credential in Node
I wasted a few hours trying to figure that out and I’m pretty sure the code I wrote wasn’t the best. Hence, I filed an issue on their GitHub, and hopefully the community would guide us the proper way of using delegated credentials.
I set an expectation after understanding various authentication mechanism available given their complete step-by-step guide. The documentation of the library is however less anticipated. As a software engineer, keeping documentation updated is perhaps one of the ways to keep users from frustration.