DEV Community

Cover image for How to log in with Google and set a schedule with Google Calendar in Express application
miminiverse
miminiverse

Posted on • Updated on

How to log in with Google and set a schedule with Google Calendar in Express application

It will be handy when we can log in with our Google Account and set up any schedule with Google Calendar

First, we will need to set up a little bit in the front end. In the login page in React, we add this function on onClick even Google sign-in button:

const handleGoogleAuthUrl = () => {
    const rootURL = "https://accounts.google.com/o/oauth2/v2/auth";
    const options = {
      redirect_uri: `${BASE_URL}/auth/google/callback`,
      client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
      access_type: "offline",
      response_type: "code",
      prompt: "consent",
      scope: [
        "https://www.googleapis.com/auth/userinfo.profile",
        "https://www.googleapis.com/auth/userinfo.email",
        "https://www.googleapis.com/auth/calendar",
      ].join(" "),
    };
    const qs = new URLSearchParams(options);
    window.location.assign(`${rootURL}?${qs.toString()}`);
  };
Enter fullscreen mode Exit fullscreen mode

BASE_URL is the backend URL for example we can use http://localhost:8000

For the scope, we can add any other scopes provided by Google, in this case, these 3 are all we need.

We use window.location.assign to redirect users to the constructed URL. We need to use the toString() method to format the object in a correct way, adding "=" between key and value pairs, and adding "&" between different parameters.

Now let's move to our backend repo. In app.ts, add this line:

app.get('/auth/google/callback', googleOauthHandler);

Next, in controllers folder, create a file called OAuth:

import User from '../models/User';
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import axios from 'axios';
import qs from 'qs';
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from '../config';

interface GoogleOauthToken {
  id_token: string;
  access_token: string;
  refresh_token: string;
}

const getGoogleOauthToken = async ({
  code,
}: {
  code: string;
}): Promise<GoogleOauthToken | undefined> => {
  const url = 'https://oauth2.googleapis.com/token';
  const values = {
    code,
    client_id: GOOGLE_CLIENT_ID,
    client_secret: GOOGLE_CLIENT_SECRET,
    redirect_uri: `${BASE_URL}/auth/google/callback`,
    grant_type: 'authorization_code',
  };
  try {
    const data = await axios.post(url, qs.stringify(values), {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    });

    return data.data;
  } catch (err) {
    console.log(err);
  }
};

const googleOauthHandler = async (req: Request, res: Response) => {
  const code: any = req.query.code;

  try {
    if (!code) {
      res.status(400).send('Invalid authorization code');
      return;
    }

    const googleOauthToken = await getGoogleOauthToken({ code });

    if (!googleOauthToken) {
      res.status(401).send('Google OAuth token not found');
      return;
    }

    const { id_token } = googleOauthToken;
    const { refresh_token } = googleOauthToken;
    const OAuthToken = refresh_token;

    if (id_token && OAuthToken) {
      const googleUser = jwt.decode(id_token) as {
        email?: string;
        name?: string;
        picture?: string;
      };

      const email = googleUser.email;
      try {
        const user = await User.findOne({ email });
        if (!user) {
          res.redirect('http://localhost:3000/login');
          return res.status(403).send('Account is not exist');
        }

        const token = user.createJWT();
        const refreshToken = user.createRefreshToken();

        await user.updateOne({ refreshToken, OAuthToken });

        res.cookie('token', refreshToken, {
          httpOnly: true,
          sameSite: 'none',
          secure: true,
          maxAge: 24 * 60 * 60 * 1000,
        });

        res.redirect('http://localhost:3000');

        return res.status(200).json({
          user: { name: user.name, userId: user._id, email: user.email },
          token,
        });
      } catch (err) {
        console.log(err);
      }
    }
  } catch (err) {
    console.log(err);
    res.redirect('http://localhost:3000/login');
  }
};

export default googleOauthHandler;
Enter fullscreen mode Exit fullscreen mode

First, assuming you already have a User model (if you do not please check our repo for reference), we will just need to add a few libraries

We will start with googleOauthHandler. Remember when we press a sign-in button in the front end, google will return us a code that we can retrieve from req.query.code. We will need that code to get our user's data, and in this case I specifically looking for the token.

We will make a POST request to https://oauth2.googleapis.com/token. We will be able to retrieve id_token from Google and then we can use jwt.decode to get all of the info we need.

We will then compare the Google email and the email in our database, if it matches we can confirm that the user exists.

The way our application is set up will only allow users to sign in if there's an email already existing in the database, if there's no email found, we will simply redirect users back to log-in page and we will not create a new user.

We will also need to retrieve the refersh_token from Google, set it as OAuthToken in User model and use it later for the Google Calendar.

Finally, since we use JWT to authenticate user, we need to create one and set a cookie to authenticate that user similar to when we sign in regular user.

If there's no token exists, we will redirect the user back to the login page

Next, we will move to the createSchedule function, first we need to import the below

import { google } from 'googleapis';
import moment from 'moment-timezone';
import User from '../models/User';
import {
  GOOGLE_CLIENT_ID,
  GOOGLE_CLIENT_SECRET,
  GOOGLE_CLIENT_URL,
} from '../config';

const oauth2Client = new google.auth.OAuth2(
  GOOGLE_CLIENT_ID,
  GOOGLE_CLIENT_SECRET,
  GOOGLE_CLIENT_URL
);
Enter fullscreen mode Exit fullscreen mode

All of these below will be in your .env file, for example:

GOOGLE_CLIENT_ID=e54954459925-ejol4bm9t5hknr.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPdX-YQEQhFv-2Bztwem9CeVBsdfsdfdsfJEKyvSCV
GOOGLE_CLIENT_URL=http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

In our project, we use TypeScript so we add types, you don't need to do so if you don't use TypeScript

const scheduleEvent = async ({
  summary,
  start,
  end,
  email,
  description,
}: {
  summary: string;
  start: Date;
  end: Date;
  email: string;
  description: string;
}) => {
  const user = await User.findOne({ email });
  if (!user) {
    throw new Error('error');
  }
  let new_refresh_token;
  if (user?.OAuthToken) {
    new_refresh_token = user.OAuthToken;
  }

  const formattedStart = moment(start).format('YYYY-MM-DDTHH:mm:ssZ');
  const formattedEnd = moment(end).format('YYYY-MM-DDTHH:mm:ssZ');

  oauth2Client.setCredentials({
    refresh_token: new_refresh_token,
  });
  const calendar = google.calendar('v3');
  const response = await calendar.events.insert({
    auth: oauth2Client,
    calendarId: 'primary',
    requestBody: {
      summary: summary,
      description: description,
      start: {
        dateTime: formattedStart,
      },
      end: {
        dateTime: formattedEnd,
      },
    },
  });
  if (!response) return 'Events failed to save in your Google Calendar';
  return 'Events successfully saved in your Google Calendar';
};

export default scheduleEvent;
Enter fullscreen mode Exit fullscreen mode

First, we need to check if the user is in our database, then we retrieve their OAuthToken. Then we will just need the start & end time for the event, and the description is optional

In this case, I'm using moment which is a date time library but if you don't want to use an external library, new Date() from Javascript would suffice

This is the link to our demo as well as the repos below

https://youtu.be/2UqX1BSH0hU

https://github.com/Mentor-Up/server

Top comments (0)