DEV Community

Ahmad Fozooni
Ahmad Fozooni

Posted on

Google OAuth2 with Fastify + TypeScript From Scratch

Introduction

Today, in a short tutorial, we plan to implement the authentication system through Google OAuth2 in a simple Fastify project with TypeScript.

There were several options on the table, but 2 options were more serious than the others. The first option is to use the @fastify/fastify-oauth2 package. The second option is to use Google Auth Library.

But because @fastify/fastify-oauth2 package is under the Fastify ecosystem, We will also use this option.

Prerequisites:

  1. Node +16.x
  2. Typescript

Steps:

In this tutorial, we will follow the steps below:

  1. Register a new project on Google Cloud Console
  2. Generate application credentials (Client ID, Client Secret)
  3. Build a simple HTML page as a Frontend side
  4. Build a simple server with Fastify + Typescript

I gave the basic explanation, so now let's get started!

Step 1: Register new project on Google Cloud Console

In the first step, we need to build our new project on the Google cloud platform.
For this, we go to the Google cloud platform panel and start according to the following sub-steps:

  1. Go to this website: Google Cloud Console

    Enter the project name (and select your location if you
    have some organization) and click on "Create"

    Google Cloud Console - Step One

  2. After that, you will get a new message in the notification area saying "Create Project: YOUR_PROJECT_NAME". Open the notification area on the right side and click on "SELECT PROJECT".

    Google Cloud Console - Step Two

  3. Now open the navigation from the left side, move your mouse on the "APIs and Services" and click on the "OAuth consent screen".

    Google Cloud Console - Step Three

  4. Now you should choose one User Type, select External user type and click on the "CREATE" button.

    Google Cloud Console - Step Four

  5. Now you have to enter your application information on this page like below:

    1. App name: Test Application
    2. User support email: select your Email Address
    3. Developer contact information: enter your Email Address

    Google Cloud Console - Step Five

    And now click on the "SAVE AND CONTINUE" button.

  6. On the next page, You can Add/Remove scopes for your application. As you know, We need to get information such as the user's first name, last name, profile picture and language to store in the database after the user has logged in with Google.

    So click on the "ADD OR REMOVE SCOPES" button and select the "…/auth/userinfo.email" and "…/auth/userinfo.profile" scopes from the opened menu and click on the "UPDATE" button at the end of the opened modal. After that.
    Now you can click on the "SAVE AND CONTINUE" button to go to the next step.

    Google Cloud Console - Step Six

  7. In the last step, you need to introduce some test users that you plan to use for Google login testing. To do this, click on the "ADD USERS" button and enter the email address of the users you want to use and click on the "SAVE" button at the end.

Now click on "SAVE AND CONTINUE" to finish the step 1 process!

OK! Done. Now proceed to the next step to create your application credentials.

Step 2: Generate Application Credentials

In the second step, we need to create our application credentials.

To do this, go to the "Credentials" under the "APIS And Services" menu, click on the "CREATE CREDENTIALS" button on the top bar and select "OAuth Client ID" option.

Google Cloud Console - Create a new credentials

Now in the newly opened form, set the items as follows:

After you click on the "CREATE" button, a new modal will be displayed for you with this title: "OAuth client created"

Be sure to copy "Client ID" and "Client secret" and save it in a safe place.

DONE! The second step is over! Now we can start coding.

Step 3: Build an simple HTML page

In the third step, create a new folder named fastify-oauth2 and create 2 other folders inside it named front and server.

Open your favorite IDE (I use Visual Studio Code) and enter the front folder.

Note: Since we are not going to go into details in this tutorial, we will use a very simple HTML page. (But keep in mind that you can create this page in any other framework such as Vue and React)

Now, Create a new html file and named it like index.html and paste the following codes into it:



<!DOCTYPE html>
<html lang="en">
<head>
    <title>Fastify + Typescript - Google OAuth2 Authentication</title>
</head>
<body>
<a href="http://localhost:8000/oauth2/google">
    Google Login
</a>
<script>
    // This function checks the current url and takes the query parameters
    function getUrlVars() {
        var url = window.location.href;
        var vars = {};
        var hashes = url.split("?")[1];
        var hash = hashes.split('&');

        for (var i = 0; i < hash.length; i++) {
            params=hash[i].split("=");
            vars[params[0]] = params[1];
        }
        return vars;
    }

    // This function create a new cookie
    function setCookie(cname, cvalue, exdays) {
        const d = new Date();
        d.setTime(d.getTime() + (exdays*24*60*60*1000));
        let expires = "expires="+ d.toUTCString();
        document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
    }

    // This statement will check if access_token query parameter is set or not
    // If it was set, it creates a new cookie with the new access token value
    if(getUrlVars()["access_token"]) {
        setCookie("google_access_token", getUrlVars()["access_token"], 1);
        console.log("Logged in successfully!")
    }
</script>
</body>
</html>


Enter fullscreen mode Exit fullscreen mode

Now serve this basic page on port 4000 (You can use in-built Live Server if you are using Visual Studio Code OR You can use Live Server package)

Our frontend side is ready to use!

Now we go to the next step, that is to build the server, to move forward together how to implement the Google OAuth2 with Fastify.

Step 4: Create the Fastify + Typescript server from the scratch

Now, open your favorite IDE again and enter the server folder.

Notes: Don't forget to go to the server path in your console terminal:



cd server


Enter fullscreen mode Exit fullscreen mode

We need to initialize the package.json there, so write this command in the console:



# If you using NPM
npm init -y

# If you using Yarn
yarn init -y


Enter fullscreen mode Exit fullscreen mode

After that, we need to install Fastify and some more dependencies, So write this 2 command in the terminal and hit the enter:



# Dependencies
yarn add fastify @fastify/oauth2 @fastify/cors axios
# Development Dependencies
yarn add -D @types/node tsx typescript


Enter fullscreen mode Exit fullscreen mode

Now initial tsconfig.json file with this command:



npx tsc --init


Enter fullscreen mode Exit fullscreen mode

Replace the content of the created ./server/tsconfig.json file with the following code:



{
  "compilerOptions": {
    "target": "es2016",
    "experimentalDecorators": true,
    "module": "commonjs",
    "resolveJsonModule": true,
    "sourceMap": true,
    "outDir": "./build",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "skipLibCheck": true
  }
}


Enter fullscreen mode Exit fullscreen mode

Now add the following scripts to the ./server/package.json file:



{
  "scripts": {
    "build": "tsc",
    "dev": "tsx watch src/app.ts"
  }
}


Enter fullscreen mode Exit fullscreen mode

After installing and configuring the project, now we review the folder structure together.

First, create a folder named src inside the server folder and create the following files & folders inside src :



fastify-oauth2
│
└───front
│   │   index.html
│   
└───server
│   │───node_modules
│   │
│   └───src
│       │───core
│       |   │    createServer.ts
│       │───modules
│       │   |───oauth2
│       │       |───google
│       |           |    google.route.ts
│       │───providers
│       |   │    cors.ts
│       |   │    oauth2.ts
│       │   app.ts
│       package.json
│       tsconfig.json
│       yarn.lock


Enter fullscreen mode Exit fullscreen mode

First of all, I have to introduce to you one of the best file and folder structures for Fastify projects.

In this structure, we will have 3 main components:

  1. Core: In this component, we build all the main structures of our application. Such as building a new server, connecting with the database and...
  2. Modules: In this component, we will break down our application into smaller pieces. So if a component needs to be developed, or needs to be accessed by a specific person, we will isolate only its features.

    For example, consider the user module. This module needs components such as controller, model, router, etc. So we can separate it piece by piece in this section. Like the example below:

- ./src/modules/user/user.controller.ts
- ./src/modules/user/user.route.ts
- ./src/modules/user/user.model.ts
- ./src/modules/user/user.schema.ts
- ./src/modules/user/user.service.ts
- ...
Enter fullscreen mode Exit fullscreen mode
  1. Providers: In this component, we can set the main and inclusive plugins in the whole system. such as auth, cors and...

After getting familiar with the structure, we will continue the training.

Open the ./src/app.ts file and paste the following codes inside it:



import { createServer } from './core/createServer';

async function startServer() {

    // Create a new instance of the server
    const server = await createServer();

    // Also you can save these values in the .env file
    // Like POST, HOST, ...
    await server.listen({
        port: 8000,
        host: "localhost",
    });

    console.log(`App is running on http://localhost:8000`);
}

// Starting the server ...
(async () => {
    await startServer();
})();


Enter fullscreen mode Exit fullscreen mode

Open the ./src/core/createServer.ts file and paste the following codes inside it:



import fastify from 'fastify';

// Import Providers
import {registerCorsProvider} from "../providers/cors";
import {registerGoogleOAuth2Provider} from "../providers/oauth2";

// Import Google OAuth2 Routes
import {googleOAuth2Routes} from "../modules/oauth2/google/google.route";

export async function createServer() {
    // Creating a new fastify instance
    const app = fastify();

    // Register the CORS provider
    registerCorsProvider(app);

    // Register the Google OAuth2 provider & routes
    registerGoogleOAuth2Provider(app);
    app.register(googleOAuth2Routes, { prefix: '/oauth2' });

    return app;
}


Enter fullscreen mode Exit fullscreen mode

Open the ./src/providers/cors.ts file and paste the following codes inside it:



import {FastifyInstance} from "fastify";
import Cors from "@fastify/cors";

// Cors Policies Options
const corsOptions = {
    // Allow all origins
    // VERY IMPORTANT: In response, the server returns an Access-Control-Allow-Origin header with Access-Control-Allow-Origin: *
    // which means that the resource can be accessed by any origin. (VERY DANGER!)
    // You can read more about in:
    // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
    origin: "*"
};

export function registerCorsProvider(app: FastifyInstance) {
    app.register(Cors, corsOptions)
}


Enter fullscreen mode Exit fullscreen mode

Open the ./src/providers/oauth2.ts file and paste the following codes inside it:



import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import OAuth2, {OAuth2Namespace} from "@fastify/oauth2";

// Register a GoogleOAuth2 namespace globally in the fastify instance
declare module 'fastify' {
    interface FastifyInstance {
        GoogleOAuth2: OAuth2Namespace;
    }
}

// Google OAuth2 Options
const googleOAuth2Options = {
    // Namespace
    name: 'GoogleOAuth2',
    // Scopes
    scope: ['profile', 'email'],
    credentials: {
        client: {
            // Put in your client id here
            id: "<CLIENT_ID>",
            // Put in your client secret here
            secret: "<CLIENT_SECRET>"
        },
        // @fastify/oauth2 google provider
        auth: OAuth2.GOOGLE_CONFIGURATION
    },
    // This option will create a new root with the GET method to log in through Google.
    // Make sure you don't have any other routes in this path with the GET method.
    startRedirectPath: '/oauth2/google',
    // Here you specify the Google callback route.
    // All logics will be checked after the success login or failure login in this path.
    callbackUri: `http://localhost:8000/oauth2/google/callback`,
    // The following 2 functions are check in detail whether the input parameters from Google include the state query parameter or not
    generateStateFunction: (request: FastifyRequest, reply: FastifyReply) => {
        // @ts-ignore
        return request.query.state
    },
    checkStateFunction: (request: FastifyRequest, callback: any) => {
        // @ts-ignore
        if (request.query.state) {
            callback()
            return;
        }
        callback(new Error('Invalid state'))
    }
};

export function registerGoogleOAuth2Provider(app: FastifyInstance) {
    app.register(OAuth2, googleOAuth2Options)
}


Enter fullscreen mode Exit fullscreen mode

Open the ./src/modules/oauth2/google/google.route.ts file and paste the following codes inside it:



import {FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest} from 'fastify';

export function googleOAuth2Routes (
    app: FastifyInstance,
    options: FastifyPluginOptions,
    done: () => void,
) {

    // https://developers.google.com/
    // Google OAuth2 JavaScript redirect URI
    app.get('/google/callback',async function (request: FastifyRequest, reply: FastifyReply) {

        // Get the access token from the Google service and save it into the token value
        const { token } = await app.GoogleOAuth2.getAccessTokenFromAuthorizationCodeFlow(request);

        // Redirect to our frontend side
        // You can get the access token from the URI Query and save it as a cookie in the client browser
        reply.redirect("http://localhost:4000/?access_token=" + token.access_token);
    });

    done();
}


Enter fullscreen mode Exit fullscreen mode

Now, you can start the server with this command:



# If you using NPM
npm run dev

# If you using Yarn
yarn dev


Enter fullscreen mode Exit fullscreen mode

Make sure that the live server of the front side is also running on port 4000.

Now go to this link: http://localhost:4000 and test the Google OAuth2 authentication that we made together in this article!

If you did everything according to the instructions, you should go back to the "http://localhost:4000" page after clicking the "Google Login" button and selecting your Google account, but with the difference that if you open your developer console panel, you will receive the "Logged in successfully!" message.

Also, if you go to the developer console panel and open the "Application" tab, you can see a new cookie with the name "google_access_token" is stored in the cookies section!

Developer Console Panel

Developer Console Panel

Now everything is ready for your Google authenticated user to use the website.

You can access the GitHub repository of this tutorial from the link below

GitHub Repository

Conclusion

Thank you for accompanying me, if you have any questions, ask them in the comments section and I will answer them as soon as I have time.

If this article was useful for you, I would appreciate it if you could show your reaction 😍

Top comments (2)

Collapse
 
vinaymanala profile image
Vinay Manala

Thanks for this article. But How do we logout of the application? Do we destroy the cookie?

Collapse
 
fozooni profile image
Ahmad Fozooni

Hello dear friend, You're welcome.

First of all, I'm so sorry for my delay.
Every OAuth2 provider only verifies your user who wants to log in via their methods. So users always be part of your community/application before trying to log out from the provider.
For example, If I log in via Google on some website, I will log out whenever I log out from the accounts I used to log in within Google.

But in general, Yes! You can remove the cookie/session to log out your user from your application.
Or, you can create some buttons to redirect a user to Google services (such as Gmail, YouTube, ...)
For example:

window.location = "https://mail.google.com/mail/u/0/?logout&hl=en";
Enter fullscreen mode Exit fullscreen mode

OR

<a href="https://mail.google.com/mail/u/0/?logout&hl=en">Logout</a>
Enter fullscreen mode Exit fullscreen mode

Enjoy your time