DEV Community

Juan Carlos Manzanero Domínguez
Juan Carlos Manzanero Domínguez

Posted on

How to integrate Google Calendar events using Google OAuth2

Google provides an API to connect your SaaS, such as Workarise, with their services, such as Google Calendar, in order to schedule events or Google Meet events. However, in order to do so, you need to prompt your users to grant access to their respective scopes. Here, we’ll show you how to do that.

Workarise Meetings feature

To sync Workarise with your Google Calendar and obtain a Google Meet link, we developed a calendar using MUI. When you click on a day, a popover will appear with the option to schedule a meeting.

If you haven’t granted Workarise permissions to access your Google Calendar, it will prompt you to do so. This will redirect you to log in with Google and accept to give access only for your calendar. If you accept, you will be redirected to the Workarise app page, which will show that the authorization was successful.

This UX flow is necessary because Google needs to authenticate the user and only grant access when the user authorizes it. For this purpose, we created a little API with Node.js to manage this flow for Workarise, utilizing the Google OAuth2 API library.

Time to work

To get started, the first step is to create a project on Google Cloud Console. After creating the project, you can navigate to the "APIs & Services" tab by clicking on the hamburger menu or using the search bar. From there, select "Enable APIs and Services." On the left sidebar, you'll find the "Credentials" tab. Click on it and then select "Create Credentials" > "OAuth Client ID" at the top of the page.

Image example showing the GCP UI to go to the OAuth Credentials

Select "Web application" as the Application type. Next, choose a name for your project and add the necessary links to authorize your project to use this client.

Image example showing the options to add a credential

Once the client is created, open it and copy the Client ID and Client Secret. These keys will be necessary for future use.

The next step is to initialize a Node.js API, which can be done by creating a new directory.

mkdir google-oauth-api && cd google-oauth-api
Enter fullscreen mode Exit fullscreen mode

Initialize npm

npm init -y
Enter fullscreen mode Exit fullscreen mode

Change your scripts in the package.json for these

"scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
Enter fullscreen mode Exit fullscreen mode

Install dependencies

npm i cors express googleapis http https morgan nodemon
Enter fullscreen mode Exit fullscreen mode

In the root folder, create a server.js file

import express from 'express';
import morgan from 'morgan';
import cors from 'cors';

const app = express();
const port = process.env.PORT || 8000;

app.use(express.json());

app.use(express.urlencoded({ extended: false }));

app.use(morgan('dev'));

app.use(
  cors({
    origin: ['http://localhost:3000'], // Here your app dev env/prod urls
  })
);

app.use((req, res, next) => {
  // set the CORS policy
  res.header('Access-Control-Allow-Origin', '*');

  // set the CORS headers
  res.header(
    'Access-Control-Allow-Headers',
    'Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers'
  );

  // set the CORS method headers
  if (req.method === 'OPTIONS') {
    res.header('Access-Control-Allow-Methods', 'GET PATCH DELETE POST');
    return res.status(200).json({});
  }

  next();
});

app.listen(port);

console.log(`Server listening on port ${port}`);
Enter fullscreen mode Exit fullscreen mode

We will use Express, Morgan, and Cors to open a port to our endpoint and configure the CORS policy. Additionally, we will set the CORS method headers to enable access to our API from our web application.

To get started, navigate to the root directory and create a new directory named src/. Inside this directory, create two additional directories named controllers/ and routes/.

Within the controllers/ directory, create a new file named googleCalendar.controller.js. This file will contain the necessary logic to interface with the Google Calendar API.

import { google } from 'googleapis';

const GOOGLE_CLIENT_ID =
  'YOUR_CLIENT_ID';
const GOOGLE_CLIENT_SECRET = 'YOUR_CLIENT_SECRET';

const oAuth2Client = new google.auth.OAuth2(
  GOOGLE_CLIENT_ID,
  GOOGLE_CLIENT_SECRET,
  'http://localhost:8000' // The root of your API
);

const calendar = google.calendar({
  version: 'v3',
  auth: 'AIzaSyAzgIbwnyoa6eeyDpSNk-8V3aEee5ND4Lc',
});

const scopes = ['https://www.googleapis.com/auth/calendar'];
Enter fullscreen mode Exit fullscreen mode

In this step, we will generate a login URL, which we will then send to your web application to be used inside an tag. It is important to set the access_type
to offlinein order to obtain a code.

export const generateAuthorizationUrl = async (req, res, next) => {
  const authorizationUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: scopes,
    include_granted_scopes: true,
  });

  res.send({ url: authorizationUrl });
};
Enter fullscreen mode Exit fullscreen mode

After the user confirms the scope access, you should redirect them to the root of the API in order to obtain a code in the URL parameters. Next, use the oAuth library to save that code and generate a refresh token. Finally, redirect the user to a specific route of your web app with the refresh token in the parameters. Once there, retrieve the refresh token and store it in your database, associated with the corresponding user.

export const redirect = async (req, res, next) => {
  const { code } = req.query;

  try {
    oAuth2Client.getToken(code, (err, tokens) => {
      if (err) {
        console.log('server 39 | error', err);
        throw new Error('Issue with Login', err.message);
      }

      const refreshToken = tokens.refresh_token; // <- Store it in your DB

            // Change the slashes to something that don't breakes the URL
      res.redirect(
        `YOUR_WEB_APP_ROUTE${refreshToken.replace(/\//g,'%2F' 
        )}`
      );
    });
  } catch (error) {
    res.redirect('YOUR_WEB_APP_ROUTE');
  }
};
Enter fullscreen mode Exit fullscreen mode

Next, from your web application, you can send an event object with the necessary data to create an event in the user's Google Calendar. You can reference the required structure here.

To ensure that the user is not repeatedly prompted for authorization, be sure to use the previously created and stored refresh token. This will allow your application to access the user's Google Calendar data without requiring additional permission grants.

export const createEvent = async (req, res, next) => {
  try {
    const { refreshToken, event } = req.body;

    oAuth2Client.setCredentials({
      refresh_token: refreshToken,
    });

    const eventResponse = await calendar.events.insert({
      auth: oAuth2Client,
      calendarId: 'primary',
      conferenceDataVersion: 1,
      requestBody: event,
    });

    res.send({ data: eventResponse.data });
  } catch (error) {
    res.send({ error: error.message });
  }
};
Enter fullscreen mode Exit fullscreen mode

Awesome! With the event ID and refresh token, you can now edit or delete events within the user's Google Calendar. This provides greater flexibility and control over the events created by your application.

export const updateEvent = async (req, res, next) => {
  const { refreshToken, eventID, event } = req.body;

  oAuth2Client.setCredentials({
    refresh_token: refreshToken,
  });

  try {
    const updateResponse = await calendar.events.patch({
      auth: oAuth2Client,
      calendarId: 'primary',
      eventId: eventID,
      resource: event,
    });

    res.send({ data: updateResponse.data });
  } catch (error) {
    console.log(error);
    res.send({ error: error.message });
  }
};

export const deleteEvent = async (req, res, next) => {
  const { refreshToken, eventID } = req.body;

  oAuth2Client.setCredentials({
    refresh_token: refreshToken,
  });

  try {
    const deleteResponse = await calendar.events.delete({
      auth: oAuth2Client,
      calendarId: 'primary',
      eventId: eventID,
    });

    res.send({ data: deleteResponse.data });
  } catch (error) {
    console.log(error);
    res.send({ error: error.message });
  }
};
Enter fullscreen mode Exit fullscreen mode

Don’t forget to add the routes!

src/routes/googleCalendar.route.js

import { Router } from 'express';
import {
  createEvent,
  generateAuthorizationUrl,
  redirect,
  updateEvent,
  deleteEvent,
} from '../controllers/googleCalendar.controller.js';

const googleOAuthRouter = Router();

googleOAuthRouter.get('/', redirect);
googleOAuthRouter.get('/generate-authorization-url', generateAuthorizationUrl);
googleOAuthRouter.post('/create-event', createEvent);
googleOAuthRouter.put('/update-event', updateEvent);
googleOAuthRouter.delete('/delete-event', deleteEvent);

export default googleOAuthRouter;
Enter fullscreen mode Exit fullscreen mode

Top comments (0)