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
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>
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
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
.
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,
- Click
ADD OR REMOVE SCOPES
- Select the first two options and click
UPDATE
to save the changes. - Click
SAVE AND CONTINUE
and complete the subsequent steps.
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:
- Select your application type, in this case, Web Application.
- Fill in the name field or go with the default name.
- 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.
- 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
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);
}
}
In this file,
- We first import the necessary dependencies.
- The
PassportStrategy
class is a module in the ‘@nestjs/passport’ package. A class calledGoogleStrategy
extends thePassportStrategy
. Note that every strategy class that uses Passport must extend thePassportStrategy
class. - In the
super
method, we instantiate theclientID
,clientSecret
, callbackURL, andscope
properties in the constructor function.- The
clientID
and clientSecret are your application‘s ID and Secret key from Google when you created the app. - The
callbackURL
is the endpoint in your app google will redirect to after authenticating a user. - The
scope
is an array of the specific user information you want to get back from google.
- The
- 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>
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',
});
}
}
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);
}
}
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,
};
}
}
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 {}
Run the app using the command below and navigate to localhost:3000/auth
to test the app
$ npm run start:dev
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)
@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!
I found a solution:
In file:
google.strategy.ts
add this function.In file:
google.strategy.ts
update constructor.In file:
google.strategy.ts
update validate function.Cool
nice
Well written @chukwutosin A great article consist well research and this is actually amazing!
Thank you! I appreeciate the feedback.
Thank you for this amazing tutorial! Can confirm that everything is up to date.
There might be upgrades in the versions of the several tools used, but it should still work just fine
How to integrate with an angular app ? Just call the route in nestjs ?
yes. just call the endpoints like you normally would in a frontend app
This was helpful. Thanks!
I'm glad you found it helpful. Thanks!
Hi @chukwutosin_ ,
Thank you very much for your amazing How To!
You're welcome!
juss started learning nestJS, nice article
Oh, that's cool! I'm glad you found it helpful
Well done, bro
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?
No, you probably missed a step. What route did you visit?
I created a route path in my user auth controller, localhost:3000//auth/google
Does it have to be just localhost:3000/
following the article. you just need to visit: localhost:3000/auth
it works, It did not work previously because I tried to access it from my swagger documentation
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?
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
Thanks
Thanks
Thanks, I'll try implement this in my next project
Great! Thanks