DEV Community

Cover image for Implement Google OAuth in NestJS using Passport
Tosin Moronfolu
Tosin Moronfolu

Posted on

Implement Google OAuth in NestJS using Passport

Introduction

Authentication is an essential part of most applications. Implementing authentication in your application depends on requirements specific to the application.

This article teaches you how to use Passport to implement Google OAuth in a NestJS application.

NestJS is a Node.js framework for building server-side applications. NestJS supports typescript out of the box. On the other hand, Passport is an authentication middleware for Node.js that supports over 500 authentication strategies, e.g., username and password, google, Facebook, etc.

Prerequisite

You need to have:

  • A basic understanding of JavaScript. TypeScript is preferred but not mandatory.
  • Basic knowledge of the NestJS framework.
  • A Google account — create one for free here.

Repository

Find the completed project on GitHub: https://github.com/folucode/google-oauth-app

Creating a NestJs Application

To get started, install Node.js if you don’t already have it installed. Download the version compatible with your Operating System and follow the installation instructions.

You can verify that Node.js is installed successfully by running the command below:

$ node -v
// v18.4.0 - node version printed to the terminal
Enter fullscreen mode Exit fullscreen mode

Next, to scaffold a NestJS project using the Nest CLI (short for command line interface), run the commands below in your terminal:

$ npm i -g @nestjs/cli
$ nest new <project-name>
Enter fullscreen mode Exit fullscreen mode

The commands above install NestJS CLI globally on your computer and creates a new project directory with your supplied project name. The created project directory has all the core NestJS starter files and modules.

To start the NestJS development server, run the command below in the project directory:

$ npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Creating an App on Google Console

Before using any Google OAuth in your project, you must create an app on Google Cloud Console. Fill in the project name, click CREATE, and navigate to the app dashboard.

Setting OAuth Consent Screen

Click on the OAuth consent screen from the sidebar, select External and click CREATE.

Setting OAuth Consent Screen

Selecting the option External means any google account can use this app. On the next page, make sure only to fill the following as this is a test application:

  • Application Name
  • User support email
  • Developer contact information

Click SAVE AND CONTINUE. On the next page,

  1. Click ADD OR REMOVE SCOPES
  2. Select the first two options and click UPDATE to save the changes.
  3. Click SAVE AND CONTINUE and complete the subsequent steps. Setting OAuth Consent Screen

Get API keys

Go to your app dashboard. At the top of the page, click on CREATE CREDENTIALS and select the OAuth client ID option. Follow the next steps:

  1. Select your application type, in this case, Web Application.
  2. Fill in the name field or go with the default name.
  3. Set the Authorized JavaScript origin and Authorized redirect URI fields. For this app, use http://localhost:3000 and http://localhost:3000/auth/google-redirect, respectively.
  4. Copy the Client ID and Client Secret or download it as JSON. The keys are always available on your app dashboard.

Setup Google OAuth

In the project directory, run the following commands:

npm i --save @nestjs/passport passport passport-google-oauth20 @nestjs/config
npm i -D @types/passport-google-oauth20
Enter fullscreen mode Exit fullscreen mode

Create a new file in the src folder named google.strategy.ts. In the file, paste the following code:

import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable } from '@nestjs/common';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor() {
    super({
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: 'http://localhost:3000/auth/google-redirect',
      scope: ['email', 'profile'],
    });
  }
  async validate(
    accessToken: string,
    refreshToken: string,
    profile: any,
    done: VerifyCallback,
  ): Promise<any> {
    const { name, emails, photos } = profile;
    const user = {
      email: emails[0].value,
      firstName: name.givenName,
      lastName: name.familyName,
      picture: photos[0].value,
      accessToken,
      refreshToken,
    };
    done(null, user);
  }
}
Enter fullscreen mode Exit fullscreen mode

In this file,

  1. We first import the necessary dependencies.
  2. The PassportStrategy class is a module in the ‘@nestjs/passport’ package. A class called GoogleStrategy extends the PassportStrategy. Note that every strategy class that uses Passport must extend the PassportStrategy class.
  3. In the super method, we instantiate the clientID, clientSecret, callbackURL, and scope properties in the constructor function.
    1. The clientID and clientSecret are your application‘s ID and Secret key from Google when you created the app.
    2. The callbackURL is the endpoint in your app google will redirect to after authenticating a user.
    3. The scope is an array of the specific user information you want to get back from google.
  4. The validate method executes after Google returns the requested user information. In this method, you decide what to do with the user information returned by Google. You then return the result using the done method.

Store keys in environment variables

In the root of the project folder, create a .env file and put the Client ID and Client Secret keys in it.

GOOGLE_CLIENT_ID= <your-client-id>
GOOGLE_CLIENT_SECRET= <your-client-secret>
Enter fullscreen mode Exit fullscreen mode

Create Google OAuth Guard

To use Google OAuth Strategy in the endpoints, you need a Guard Class that extends the AuthGuard class and specifies google as the strategy to use.

Now, in the src folder, create a google-oauth.guard.ts file and paste the following code in it:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class GoogleOAuthGuard extends AuthGuard('google') {
  constructor(private configService: ConfigService) {
    super({
      accessType: 'offline',
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Note: we specify accessType to be offline so that Google can return a refresh token after successful authentication.

Create Auth routes

Open the app.controller.ts file in the src folder and replace its content with the code below. In this code, there are two routes. The first initializes Google authentication, and the other is the callback Google calls after authentication.

Also, notice that both routes use the GoogleOAuthGuard.

import { GoogleOAuthGuard } from './google-oauth.guard';
import { Controller, Get, Request, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';

@Controller('auth')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  @UseGuards(GoogleOAuthGuard)
  async googleAuth(@Request() req) {}

  @Get('google-redirect')
  @UseGuards(GoogleOAuthGuard)
  googleAuthRedirect(@Request() req) {
    return this.appService.googleLogin(req);
  }
}
Enter fullscreen mode Exit fullscreen mode

Note: The googleLogin method doesn’t exist yet. You would get an error for that.

Return the user data after login

In the app.service.ts file, replace the contents with this code code:

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  googleLogin(req) {
    if (!req.user) {
      return 'No user from google';
    }

    return {
      message: 'User information from google',
      user: req.user,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

The googleLogin method returns either a ‘No user from google’ error if the authentication failed or the user information if the authentication is successful.

Tie it all together

The app can’t know to use GoogleStrategy until you tell it. In the app.module.ts file, import the GoogleStrategy class and add it as a service in the providers array.

You also need to load the environment variables into the app. Do that by importing the ConfigModule class from the “@nestjs/config” package and call its forRoot method in the imports array.

import { GoogleStrategy } from './google.strategy';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule.forRoot()],
  controllers: [AppController],
  providers: [AppService, GoogleStrategy],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Run the app using the command below and navigate to localhost:3000/auth to test the app

$ npm run start:dev
Enter fullscreen mode Exit fullscreen mode




Conclusion

This article showed you how to implement Google OAuth sign-in using Passport in NestJS applications.

Resources

Connect

Feel free to connect with me across my social media handles

Top comments (30)

Collapse
 
matias_c85bccf34e791038ff profile image
Matias • Edited

@chukwutosin_ At this point how can I handle state?

I need to pass a custom parameter from the frontend, and get this parameter on backend redirection.

This is to save extra on my DB apart of the data google give me.

Thank you so much!

Collapse
 
matias_c85bccf34e791038ff profile image
Matias • Edited

I found a solution:

In file: google.strategy.ts add this function.

async authenticate(req: Request) {
    if (req.query.myData) {
      // /auth
      return super.authenticate('google', {
        state: req.query.myData,
      });
    }
    // /auth/callback
    return super.authenticate(req);
  }
Enter fullscreen mode Exit fullscreen mode

In file: google.strategy.ts update constructor.

constructor() {
    super({
      clientID: '',
      clientSecret: '',
      callbackURL: '',
      scope: ['email', 'profile'],
      passReqToCallback: true, // <---- add this.
    });
  }
Enter fullscreen mode Exit fullscreen mode

In file: google.strategy.ts update validate function.

  async validate(
    req: Request, // <---- add this.
    accessToken: string,
    refreshToken: string,
    profile: Profile,
    done: VerifyCallback,
  ): Promise<void> {
...

console.log(req.query.state); // <----- Your data will be here.
Enter fullscreen mode Exit fullscreen mode
Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

Cool

Collapse
 
ht35 profile image
Huy Tran

nice

Collapse
 
devgancode profile image
Ganesh Patil

Well written @chukwutosin A great article consist well research and this is actually amazing!

Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

Thank you! I appreeciate the feedback.

Collapse
 
alexkar13 profile image
Alex Karydis

Thank you for this amazing tutorial! Can confirm that everything is up to date.

Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

There might be upgrades in the versions of the several tools used, but it should still work just fine

Collapse
 
denernun profile image
Dener Rocha

How to integrate with an angular app ? Just call the route in nestjs ?

Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

yes. just call the endpoints like you normally would in a frontend app

Collapse
 
abhikbanerjee99 profile image
Abhik Banerjee

This was helpful. Thanks!

Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

I'm glad you found it helpful. Thanks!

Collapse
 
jesseazevedo profile image
Jessé Azevêdo

Hi @chukwutosin_ ,

Thank you very much for your amazing How To!

Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

You're welcome!

Collapse
 
igeorge17 profile image
iGEORGE17

juss started learning nestJS, nice article

Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

Oh, that's cool! I'm glad you found it helpful

Collapse
 
shifoo28 profile image
shifoo28

Well done, bro

Collapse
 
seghodavid profile image
Seghosimhe David

When running locally, the first route does not take me to the google authentication page, is that how it's supposed to be on localhost?

Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

No, you probably missed a step. What route did you visit?

Collapse
 
seghodavid profile image
Seghosimhe David

I created a route path in my user auth controller, localhost:3000//auth/google
Does it have to be just localhost:3000/

Thread Thread
 
chukwutosin_ profile image
Tosin Moronfolu

following the article. you just need to visit: localhost:3000/auth

Thread Thread
 
seghodavid profile image
Seghosimhe David

it works, It did not work previously because I tried to access it from my swagger documentation

Thread Thread
 
seghodavid profile image
Seghosimhe David

I have a question if you don't mind, In a case where you do not need a refreshToken, what other option can be passed to super() aside accessType?

Thread Thread
 
chukwutosin_ profile image
Tosin Moronfolu

Any other Google specific options can be passed there. Every other option can be found on Google's documentation which I linked at the end of the article

Thread Thread
 
seghodavid profile image
Seghosimhe David

Thanks

Collapse
 
seghodavid profile image
Seghosimhe David

Thanks

Collapse
 
mundzirmuin profile image
mundzirmuin

Thanks, I'll try implement this in my next project

Collapse
 
chukwutosin_ profile image
Tosin Moronfolu

Great! Thanks