In this article, we are going to implement (OAuth) login with google in Nest JS. Coming from Express, implementing OAuth in Nest JS may seem not so straight forward especially when using the general passport module because, in Nest JS, so many things have been implemented and wrapped in various nest modules that can be used out of the box by developers building in this awesome framework. While this is is a very good thing, you have to take the time to figure out how some of the regular things work differently in Nest.
Nest JS uses Typescript but allows the use of vanilla JS so it does not really force developers to write in typescript.
Nest is built on the popular Express framework and some of the concepts are very familiar but if you have never worked with Nest and want to get more from this article then I suggest that you take a few minutes to familiarize yourself with the framework here, the overview section will definitely get you going quickly.
Prerequisites
To follow through this tutorial you must have the following:
- Node JS
- NPM
- Web Browser
- Code Editor (VsCode)
- Gmail Account
If you don’t have Node.js installed just head on to the official Node.js website to get a copy of Node.js for your platform. Once you install Node.js you will automatically have npm installed.
Getting Started
To get started, we are going to scaffold a new nest project using the nest cli so we’ll install it globally by running the following command on the terminal:
npm i -g @nestjs/cli
Creating a new Nest project
Since we have just installed nest cli globally, we can use it to setup a new Nest project server by running the following command:
cd desktop && nest new google-login && cd google-login
Open the generated project folder in your editor of choice which should look like the one below:
Install dependencies
For this project we will be using passport, dotenv to manage our environment variables, so let’s install them by running the following:
npm install --save @nestjs/passport passport passport-google-oauth20 dotenv
npm install -D @types/passport-google-oauth20
Test the server by running: ```npm
run start:dev
Now open up your browser and type the localhost URL on which Nest is running **‘localhost:3000/’** you should get **Hello world** just as shown below:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/an1wvy071ct9rgbscgi3.png)
Now we are good to go 🚀
### Create the Google Application
To use google OAuth we have to create an application on google developer console hence the need for the Gmail account. Visit [https://console.developers.google.com/](https://console.developers.google.com/) and create an application which we will use to set up the authentication in Nest JS. So when you visit the google developer console URL you should see something similar to the screen below
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gfwevy9t1tmjuzug40gw.png)
Click on **“NEW PROJECT”** and fill in your desired name and then click the Create button.
**Set Project Consent Screen**
The project consent screen is what is displayed to the user whenever they want to use our google application to login to our system. To set the consent screen
click “OAuth consent screen” menu at the sidebar
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yoi62c37kbw01gnx2mmf.png)
Select External so the application can be used by any google account and then click **CREATE**.
On the consent screen, make sure you only fill the “Application Name” and nothing else since this is just for testing purposes. If we are creating a live application then other fields can be filled which will then need to go through the approval process. Click save when you are done.
**Get App credentials**
To get our app credentials which will be used to authenticate the app with google API click on “Credentials” menu at the sidebar.
* Click Create credentials and select OAuth Client ID
* Select Web applications on the next screen then fill the Authorized JavaScript origins and the redirect URI.
The Authorized JavaScript origins refers to where our request will be coming from which in this case is localhost, so enter **http://localhost:3000** and for the Authorized redirect URIs enter **http://localhost:3000/google/redirect**.
Kindly note that the redirect URI simply refers to the particular endpoint in our app where google will return the response after authenticating a user.
Click save when you’re done. You should get your app credentials from the screen below
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wsndg61fgc97gmq2ltvx.png)
Copy the credentials and save it somewhere because we are going to use it in our app.
### Setup Google Login (OAuth)
The first thing to do is to setup the google strategy which is the core of our google login implementation. Create a file named google.strategy.ts in the src folder and add the following code into the file.
In the code above, we loaded in all needed dependencies and then created a GoogleStrategy class as a sub-class of the PassportStrategy class. Every individual class that we define which uses passport must extend the PassportStrategy class which is a dedicated module from the ‘@nestjs/passport’.
We then pass in all the required parameters for the google strategy.
CLIENT_ID and CLIENT SECRET are the application ID and SECRET we got from google when we created the application which was loaded in through the environment variable.
CallbackURL is the particular endpoint in our app which google will return control to after authenticating a user. Remember we defined this also on google while creating our application.
Scope refers to the set of user information that we require from google needed in our app. In this case, basic user information captured in the profile and the user email.
The validate method refers to the verify callback function that will be executed after google returns to us the requested user information. This is where we decide what we want to do with the user information, in this case, we are just extracting and formatting the information we need from the returned user profile and adding it to the user object which will be available on the global request object. This is done by calling done and passing into it null (which means no error) and the user object.
Don’t forget to add the environment variables just as shown below in a .env file at the root of the project:
Note:
We could easily do all we want to do with the user information in the strategy file but Nest JS is very big on Single Responsibility Principle and since ordinarily in a live application, we will most likely want to save the user information in the database, such kind of actions is dedicated to something called services in Nest JS.
Setup the Controller, Route, and Service
For us to be able to login with google, we must setup the appropriate endpoint in our application and this is the job of controllers in Nest JS. To do this, open up the app.controller.ts file in the src folder and replace the content with the following code.
In Nest JS, routes can be setup at the controller level and/or at the request method level so in the code above we setup the google login route at the controller decorator level which means that every request in the controller will go through the google endpoint. You can read more on routing in Nest JS here
The first Get request is the endpoint that activates the google authentication through a special Guard from the “@nestjs/passport” module placed on the endpoint called “AuthGaurd”. We pass in ‘google’ as the argument to this AuthGuard to signify that we want to use the google strategy from the passport module to authenticate the request on this endpoint.
The second Get request refers to the second endpoint where google will redirect to (redirec URL) after authenticating a user. This endpoint also uses the special AuthGuard. After the done function in the validate method from the google strategy file is called, control is returned back to the googleLogin function on this controller. Let’s create the googleLogin function.
Open the app.service.ts file in the src folder and add the following code
Here we are just returning back the user information from google which was added to the request object from the validate method in the google strategy file.
Bringing it all together
As of now, our application still doesn’t know of the google strategy that was setup, so we need to make it available in the app module before we can use it.
Open the app.module.ts file and add the google strategy as a service in the provider array. Your app module file should look like the one below
Testing Our App
Start the app by running ```npm
run start:dev
Launch any browser on your computer and visit the google endpoint at localhost:3000/google
You should see something similar to the screen below
User information from google after login is shown below
Congratulations! You have just successfully implemented Google OAuth (Login with Google) in a Nest JS application.
Completed code can be found here: https://github.com/iMichaelOwolabi/google-oauth-nestjs
You can drop your comments here if you have one and for further engagements, I can always be reached on Twitter @iMichaelOwolabi
Top comments (52)
@imichaelowolabi,
done
callback should not be used invalidate
method, because it is invoked internally by nestjs/passport. It is better to just return user object from validate:Thanks for this detailed explanation.
I am working on angular + nestJs project, which are configured at different ports (localhost:4200 and localhost:8080). I am trying to request from angular to nestJs.
But it is not redirected on CallbackUrl, rather it is giving me some CORS error. However,
I have put, localhost:8080 and my endpoint at authorized URI and also configured app.enablecors() in main.js
I am having this error instead,
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fv1%2Fauth%2Fredirect&scope=email%20profile&client_id=XXX' (redirected from 'localhost:8080/v1/auth') from origin 'localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
....
However, after clicking that redirection link manually, it is working properly. Did I miss anything?
Any help is appreciated.
Hi Kaivan,
Thanks for the feedback. As per the error you're getting, this simply has to do with the "Authorized Javascript origin" and the redirect URI. Kindly crosscheck these two values from your google console to ensure they are the correct endpoints for your frontend and backend app.
Thanks for your reply,
As I am requesting from angular and default port is 4200, I have given localhost:4200 in authorized JavaScript uri and I am redirecting it to the backend, thus I have given localhost:8080 over there in the redirect uri.
Kindly can refer this picture.
dev-to-uploads.s3.amazonaws.com/up...
Hello Kaivan,
Did you find a solution? cuz I have the same problem. I'm using nextJs and I have a cors problem even if I've enabled it
Hello WassilaBen,
Did you find a solution? cuz I have the same problem too...😅
vcs resolveram o problema? como?
In your Nest Js app open your main.ts add this
after creating an instance of nest application
Change the origin to localhost:8080
I've faced the same issue, please share your solution if you find finally.
vcs resolveram o problema? como?
did you find a solution because i got the same problem
(redirected from 'localhost:3000/google') from origin 'localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
InternalOAuthError: Failed to obtain access token
at GoogleStrategy.OAuth2Strategy._createOAuthError (C:\Work_test\google-oauth-nestjs\node_modules\passport-oauth2\lib\strategy.js:408:17)
i just test github.com/iMichaelOwolabi/google-...
I got the correct google account login interface and logged in successfully, but reported an error when calling back the localhost:3000/google/redirect?cod....
How can I tell if I have a problem with my google project settings or JS code?
Hi Zicjin, try to reconfirm your redirect URL both on google and in your code as well as the scope. Sometimes when asking for non-existent scope, you may get such error.
Please let me know how it goes.
For others: double check your client ID and secret for the typos.
I am getting the following error after applying this method.
node_modules/apollo-server-core/dist/ApolloServer.d.ts:5:8 - error TS1259: Module '"/Users///node_modules/@types/ws/index"' can only be default-imported using the 'esModuleInterop' flag
5 import WebSocket from 'ws';
~~~~~~~~~
node_modules/@types/ws/index.d.ts:270:1
270 export = WebSocket;
~~~~~~~~~~~~~~~~~~~
This module is declared with using 'export =', and can only be used with a default import when using the 'esModuleInterop' flag.
Sorry, this Error is not related to this module.
Thanks for the clarification Humayun
I am getting following error when I ran npm run start
src/app.controller.ts:1:49 - error TS2307: Cannot find module '@nestjs/common' or its corresponding type declarations.
1 import { Controller, Get, Req, UseGuards } from '@nestjs/common';
~~~~
~~~~src/app.module.ts:1:24 - error TS2307: Cannot find module '@nestjs/common' or its corresponding type declarations.
1 import { Module } from '@nestjs/common';
~
~~~~~~~src/app.service.ts:1:28 - error TS2307: Cannot find module '@nestjs/common' or its corresponding type declarations.
1 import { Injectable } from '@nestjs/common';
~~~~
~~~~src/google.strategy.ts:5:28 - error TS2307: Cannot find module '@nestjs/common' or its corresponding type declarations.
5 import { Injectable } from '@nestjs/common';
~
~~~~~src/google.strategy.ts:14:17 - error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try
npm i --save-dev @types/node
.14 clientID: process.env.GOOGLE_CLIENT_ID,
~~
~~~~src/google.strategy.ts:15:21 - error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try
npm i --save-dev @types/node
.15 clientSecret: process.env.GOOGLE_SECRET,
~~
src/main.ts:1:29 - error TS2307: Cannot find module '@nestjs/core' or its corresponding type declarations.
1 import { NestFactory } from '@nestjs/core';
~~~~
~~../node_modules/@nestjs/passport/dist/auth.guard.d.ts:1:29 - error TS2307: Cannot find module '@nestjs/common' or its corresponding type declarations.
1 import { CanActivate } from '@nestjs/common';
~~~~
~~~~../node_modules/@nestjs/passport/dist/interfaces/auth-module.options.d.ts:1:38 - error TS2307: Cannot find module '@nestjs/common/interfaces' or its corresponding type declarations.
1 import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
~
~~~~~~~~~~../node_modules/@nestjs/passport/dist/passport.module.d.ts:1:31 - error TS2307: Cannot find module '@nestjs/common' or its corresponding type declarations.
1 import { DynamicModule } from '@nestjs/common';
~~~~
~~~~../node_modules/dotenv/types/index.d.ts:2:23 - error TS2688: Cannot find type definition file for 'node'.
2 ///
~~~~
../node_modules/dotenv/types/index.d.ts:23:17 - error TS2580: Cannot find name 'Buffer'. Do you need to install type definitions for node? Try
npm i --save-dev @types/node
.You miss install
@nestjs/core
and@nestjs/common
, and don't forget to install @types/node an dev dependencies 😅hi, thanks for the code works perfectly;).
on the other hand I have a question when the page reloads I have an
Error Server:
error: TokenError: Bad Request
at GoogleStrategy.OAuth2Strategy.parseErrorResponse,
Page:
statusCode: 500
message: "Internal server error",
how would you solve the problem? thank you so much.
Hi, have same issue, could u help with solving this error?
How to load ClientID, ClientSecret and CallbackURL from database instead of loading from env in this example. Here is the code where you are loading it from env.
I am facing this error when login with google
Any body face this issue and resolved it?
{"statusCode":404,"message":"Cannot GET /google/redirect?code=4/0AX4XfWg_370s0nW_gRVvaBql7g8u0TPVmgoLkly22yTxM7JIccz0-tFAht0ELvLn6BOQ7A&scope=email%20profile%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile%20openid&authuser=0&prompt=consent","error":"Not Found"}
Hi,
From the error message you shared, it seems the redirect endpoint doesn't exist or doesn't have use the right HTTP method in your app.
You should confirm that you have the google redirect endpoint setup in your application with the right GET http method and try again
Thx for the Article,
But i have an problem with this.
I am using graphl with this first i had an problem that i had not an Response object i fixed this with
but now i dont get any real response at all.
i debuged alot and found out that the google-strategy never calls success, error or fail to realy end the transaction.
the only function that calls these is self._oauth2.getOAuthAccessToken
but this gets also never called.
i also could not find where this function is called at all i search for this function in my node_modules folder and only found the declaration.
Thx for Help
Greetings
i think i found an fix for that.
the problem is that the oauth2 strategy is looking for req.query.code
so one fix is just write the token in the req.query.code in the getRequest function
did think that this fixes the issue but no.
does anyone has experience with nestjs + passport (google) and graphql?
Hi there! thanks for the article, but I keep having this error that I can't fix. I get:
"Access blocked: Authorization Error
Missing required parameter: scope
Error 400: invalid_request"
I do have the scope parameter, and I have my callback URL also, any other ideas? I don't know what else to try.