Wednesday, November 2, 2022
HomeWeb DevelopmentImplementing safe single sign-on authentication in NestJS

Implementing safe single sign-on authentication in NestJS


NestJS is a progressive Node.js framework for constructing environment friendly, dependable, and scalable server-side purposes. On this tutorial, we are going to discover ways to implement a safe Google single sign-on (SSO) within the NestJS backend service and join it to a React frontend software to authenticate customers in a easy net software.

Soar forward:

Organising the consumer and server folders in NestJS

To get began, we’ll create the frontend consumer and backend server folders. Subsequent, we’ll navigate to the consumer folder and initialize the React software by operating the command npx create-react app react-nestjs within the terminal. This may create all the required recordsdata for our frontend app.

For the backend aspect, we are going to run the nest new server command within the terminal. This may scaffold a brand new NestJS challenge listing and populate the listing with the preliminary core NestJS recordsdata and supporting modules.

The folder listing ought to seem like this:

Nest.js Project Directory

Then, we are going to navigate to consumer and run npm run begin to get our frontend app up and operating. This may begin an area growth server as localhost:3000.

Beginning the event server

To start our backend growth server, we’ll navigate to the server folder and run npm run begin. As a result of we’ve already used the localhost:3000, we are going to change the port for the backend to localhost:8080 within the major.ts file:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async perform bootstrap() {
 const app = await NestFactory.create(AppModule);
 await app.hear(8080);
}
bootstrap();

The npm run begin will begin the event server for us at localhost:8080 and show “Hi there, World!” from the app.controller.ts file.

The app.controller.ts makes use of the getHello methodology outlined in app.service.ts to return the textual content when the event server begins at localhost:8080. While you verify the app.controller.ts, it’s best to see this:

import { Controller, Get, Submit, Physique } from '@nestjs/frequent';
import { AppService } from './app.service';
@Controller()
export class AppController {
 constructor(non-public readonly appService: AppService) {}

 @Get()
 getHello(): string {
   return this.appService.getHello();
 }

That is the consequence within the app.service.ts file:

import { Injectable } from '@nestjs/frequent';
@Injectable()
export class AppService {
  getHello(): string {
   return 'Hi there World!';
 }

You’ll be able to learn extra concerning the controllers in NestJS right here.

Making a safe SSO challenge on the Google Cloud Platform

After scaffolding our app, we’ll arrange a brand new challenge on the Google Cloud Platform. First, we’ll navigate to the console and create a brand new challenge by choosing Undertaking Bar within the prime left nook. Then, click on New Undertaking and enter your challenge particulars. After that, seek for credentials within the search bar and choose API and Companies.

Then, we’ll configure the content material display by clicking the Configure Consent Display screen earlier than creating our credentials. Subsequent, choose Configure Display screen and enter the app data. Subsequent, we’ll choose Save and Proceed. Then, click on Credentials, Create Credentials, and choose OAuth consumer ID.

This may immediate us to a web page to create an OAuth Consumer ID, the place we are going to choose Internet software because the Software Kind. Then, we are able to add the JavaScript origins and the redirect URIs as locahost:3000. We’ll additionally add a second URI with the worth http://localhost. Lastly, click on Create to get the consumer ID and the consumer secret:

NestJS Secure Sign On With Google

Configuring React OAuth 2.0 for Google sign-on

To arrange the Google Auth Library, we are going to use React OAuth 2.0. To get began, let’s run the command npm i @react-oauth/google within the terminal. After creating our consumer ID, we’ll wrap the house web page with the GoogleOAuthProvider.

Then, inside App.js, we are going to clear up the bootstrapped code and exchange it with the next:

import "./App.css";
import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google";

perform App() {
 return (
   <div className="App">
         <h1>Welcome</h1>
         <GoogleOAuthProvider      clientId="192710632585-6dvq7283tr2pug31h5i6ii07fgdeu6q3.apps.googleusercontent.com
"
         >
           <GoogleLogin
             onSuccess={async (credentialResponse) => {
             console.log(credentialResponse);
                        }}
             onError={() => {
               console.log("Login Failed");
             }}
           />
         </GoogleOAuthProvider>
   </div>
 );
}

export default App;

Right here, we used GoogleOAuthProvider and the GoogleLogin APIs to implement the Google SSO performance. Then, we logged the credential response from GoogleLogin within the console. We additionally handed the clientId to the GoogleOAuthProvider.

Utilizing the clientId

Now, we’ll register with our e-mail and verify the console for the logged credential response. Within the console, we should always have a clientId response object:

Example of NestJS ClientID Response Object

Lastly, now we have to retailer the clientId that we’re passing to GoogleOAuthProvider in an atmosphere variable file. As a result of it’s a Secret ID, it shouldn’t be instantly uncovered to the browser or pushed to the repository alongside different code.

Subsequent, create a .env file within the consumer folder, and add .env to .gitignore so the file is ignored when pushed to the repository. Inside .env, create a REACT_APP_GOOGLE_CLIENT_ID variable and cross it within the clientId uncooked worth:

# google consumer id
REACT_APP_GOOGLE_CLIENT_ID=192710632585-6dvq7283tr2pug31h5i6ii07fgdeu6q3.apps.googleusercontent.com

It’s vital to notice that the variable identify must be prefixed with the REACT_APP key phrase.

Then, in App.js, we are going to cross course of.env.REACT_APP_GOOGLE_CLIENT_ID as the worth for clientId for React to learn .env:

import "./App.css";
import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google";
perform App() {
 return (
   <div className="App">
         <h1>Welcome</h1>
         <GoogleOAuthProvider
           clientId={course of.env.REACT_APP_GOOGLE_CLIENT_ID}
         >
           <GoogleLogin
             onSuccess={async (credentialResponse) => {
               console.log(credentialResponse);
                         }}
             onError={() => {
               console.log("Login Failed");
             }}
           />
         </GoogleOAuthProvider>
       </div>
 );
}

export default App;

State administration with Zustand to your NestJS challenge

We’ll use Zustand for our international state administration. Nevertheless, you should use Redux, Recoil, or any state administration of your selection. To get began, run npm set up zustand to put in the bundle:

Then, we are going to create a hook folder and create the useStore.js retailer file:

import create from "zustand";

export const useStore = create((set) => ({
 // get the information from native storage
 authData: localStorage.getItem("authData")
   ? JSON.parse(localStorage.getItem("authData"))
   : null,

 setAuthData: (authData) => {
   localStorage.setItem("authData", JSON.stringify(authData));
   set({ authData });
 },
}));

Subsequent, we’ll make an Axios POST request utilizing the Axios.publish methodology each time a consumer logs in.

First, set up Axios with npm set up axios and make a Submit request when choosing GoogleLogin:

import "./App.css";
import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google";
import axios from "axios";
import { useStore } from "./hooks/useStore";

perform App() {
 const setAuthData = useStore((state) => state.setAuthData);
 return (
   <div className="App">
              <h1>Welcome</h1>
         <GoogleOAuthProvider
           clientId={course of.env.REACT_APP_GOOGLE_CLIENT_ID}
         >
           <GoogleLogin
             useOneTap={true}
             onSuccess={async (credentialResponse) => {
               console.log(credentialResponse);
               const { knowledge } = await axios.publish(
                 "http://localhost:8080/login",
                                );
               localStorage.setItem("AuthData", JSON.stringify(knowledge));
               setAuthData(knowledge);
             }}
             onError={() => {
               console.log("Login Failed");
             }}
           />
         </GoogleOAuthProvider>   
      </div>
 );
}

export default App;

Keep in mind, we set the port for our backend as 8080, so we’ll request 8080/login later in our backend. Then, we’ll retailer the information from the backend server in native storage and set setAuthData to the information that our backend service will return.

Constructing the NestJS folder

Subsequent, we have to arrange our NestJS folder within the server we created earlier:

NestJS SSO Server Folder

To verify that we efficiently related NestJS to the frontend app, we are going to return React x NestJS in app.controller.ts utilizing the POST HTTP request methodology:

import { Controller, Submit } from '@nestjs/frequent';
import { AppService } from './app.service';
@Controller()
export class AppController {
 @Submit('/login')
 login: string {
   return 'Reaxt x NestJS';
 }
}

Now, let’s log in to the frontend app and verify the browser’s native storage:

Checking the NestJS Browser Storage

As soon as we log in efficiently, we are able to ship the response as a textual content. From right here, we’ll create a schema and ship the identify, e-mail, image consumer objects.

Keep in mind, for the connection between NestJS and React to work, we have to allow cors within the NestJS major.ts file. In any other case, there can be an error when calling the backend endpoint:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async perform bootstrap() {
 const app = await NestFactory.create(AppModule);
 //allow cors
 app.enableCors({
   origin: 'http://localhost:3000',
 });
 await app.hear(8080);
}
bootstrap();

As a result of our port origin is localhost:3000, we are going to cross it to the origin within the enableCors methodology.

Implementing Google OAuth in NestJS

We’ll begin with the Google Auth Library: Node.js Consumer on the server aspect. To put in this bundle, run npm i google-auth-library and import the OAuth2Client to retrieve an entry token, then refresh the token.

Subsequent, we are going to provoke a brand new OAuth2Client occasion, cross clientId, and generate the key when establishing the Google Cloud Platform.

Once more, we don’t wish to cross the values plainly in our code, so we have to implement .env for the server aspect. First, create an .env and embrace it in .gitignore. Then, add the 2 variables within the .env file:

GOOGLE_CLIENT_ID=192710632585-6dvq7283tr2pug31h5i6ii07fgdeu6q3.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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

Now, we have to load and parse .env from the challenge root listing. We’ll additionally merge key-value pairs from .env with the atmosphere variables and retailer the end in a personal construction accessed by the ConfigService. To do that, we are going to set up npm i --save @nestjs/config which makes use of dotenv internally.

Then, we’ll import the ConfigModule from nest/config into the basis AppModule and management its conduct utilizing the .forRoot() static methodology:

import { Module } from '@nestjs/frequent';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';

@Module({
 imports: [
   ConfigModule.forRoot({
     isGlobal: true,
     envFilePath: '.env',
   }),
  ],
 controllers: [AppController],
 suppliers: [AppService],
})
export class AppModule {}

Now, we are able to learn our .env variables from wherever contained in the challenge. Subsequent, we’ll implement the Google Authentication in app.controller.ts:

import { Controller, Get, Submit, Physique } from '@nestjs/frequent';
import { AppService } from './app.service';
import { OAuth2Client } from 'google-auth-library';

const consumer = new OAuth2Client(
 course of.env.GOOGLE_CLIENT_ID,
 course of.env.GOOGLE_CLIENT_SECRET,
);

@Controller()
export class AppController {
 @Submit('/login')
 async login(@Physique('token') token): Promise<any> {
   const ticket = await consumer.verifyIdToken({
     idToken: token,
     viewers: course of.env.GOOGLE_CLIENT_ID,
   });
    // log the ticket payload within the console to see what now we have
   console.log(ticket.getPayload());
    }
}

Right here, we cross the idToken and viewers to the Physique decorator, the equal of req.physique. The viewers is the clientId and the token will come from our credentialObject on the frontend.

We’ll cross the token in App.js because the credentialObject.credentials to cross it alongside Physique:

<GoogleLogin
             useOneTap={true}
             onSuccess={async (credentialResponse) => {
               console.log(credentialResponse);
               const { knowledge } = await axios.publish(
                 "http://localhost:8080/login",
                 {
                   // cross the token as a part of the req physique
                   token: credentialResponse.credential,
                 }
               );
               localStorage.setItem("AuthData", JSON.stringify(knowledge));
               setAuthData(knowledge);
             }}
             onError={() => {
               console.log("Login Failed");
             }}
           />

Once we efficiently log in, we should always have this in our terminal because the ticket.getPayload() knowledge:

NestJS SSO Terminal

Saving our NestJS SSO challenge knowledge to MongoDB

For this tutorial, we are going to use MongoDB to save lots of customers’ data within the database. To get began, enroll or log in to a MongoDB Cloud Companies account. Then, create a brand new free-tier cluster:

Adding NestJS to the MongoDB Database

Creating the NestJS Shared Cluster

Constructing the consumer schema

After creating the cluster, join together with your software to get your MONGOURI. Copy the URI and paste it into the .env file for the server. Then, we are going to join our database with our NestJS app contained in the app.module.ts.

We’ll create a consumer schema by creating the consumer.schema.ts file. You’ll be able to learn extra about connecting the MongoDB database in NestJS right here:

MONGO_URI=mongodb+srv://Taofiq:[email protected]/?retryWrites=true&w=majority


import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Doc } from 'mongoose';

export sort UserDocument = Consumer & Doc;

@Schema()
export class Consumer {
 @Prop()
 identify: string;

 @Prop()
 e-mail: string;

 @Prop()
 picture: string;
}

export const UserSchema = SchemaFactory.createForClass(Consumer);

In our schema, we’ll ship the identify, e-mail, and picture props as a response when a login request is made to the Submit.

Now, within the app module, we are going to configure the MongoDB connection:

import { Module } from '@nestjs/frequent';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { Consumer, UserSchema } from './consumer.schema';

@Module({
 imports: [
   ConfigModule.forRoot({
     isGlobal: true,
     envFilePath: '.env',
   }),
   MongooseModule.forRoot(process.env.MONGO_URI),
   MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
 ],
 controllers: [AppController],
 suppliers: [AppService],
})
export class AppModule {}

Making use of the frontend safe single sign-on with knowledge from NestJS

Transferring on, we are going to create an app service to create new customers and log current customers into the app:

import { Injectable } from '@nestjs/frequent';
import { InjectModel } from '@nestjs/mongoose';
import { Mannequin } from 'mongoose';
import { Consumer, UserDocument } from './consumer.schema';

@Injectable()
export class AppService {
 constructor(@InjectModel(Consumer.identify) non-public userModel: Mannequin<UserDocument>) {}
 async login({
   e-mail,
   identify,
   picture,
 }: {
   e-mail: string;
   identify: string;
   picture: string;
 }): Promise<any> {
   const consumer = await this.userModel.findOne({ e-mail: e-mail });
   if (!consumer) {
     const newUser = new this.userModel({ e-mail, identify, picture });
     await newUser.save();
     return newUser;
   } else {
     console.log(consumer);
     return consumer;
   }
 }
}

Right here, we’ll create a brand new login perform. First, we’ll confirm the existence of a consumer’s e-mail. If they aren’t within the database, create a brand new consumer with the userModel. If the consumer already exists within the database, we are going to return the prevailing consumer’s identify, e-mail, and picture when the endpoint known as. From right here, a ticket is created and the token and viewers values are handed in:

{
                   // cross the token as a part of the req physique
                   token: credentialResponse.credential,
                 }

Then, in our app.controller.ts, we are going to use login through the use of the token we handed in App.js. Right here’s a recap of the ticket.getPayload() object knowledge for when a consumer efficiently logs in:

Successful NestJS SSO Ticket Object Data

Since our schema relies on identify, e-mail, and picture, we are able to destructure these values from ticket.getPayload and use them within the login:

@Submit('/login')
 async login(@Physique('token') token): Promise<any> {
   const ticket = await consumer.verifyIdToken({
     idToken: token,
     viewers: course of.env.GOOGLE_CLIENT_ID,
   });
   console.log(ticket.getPayload(), 'ticket');
   const { e-mail, identify, image } = ticket.getPayload();
   const knowledge = await this.appService.login({ e-mail, identify, picture: image });
   return {
     knowledge,
     message: 'success',
   };
 }

When a consumer tries to log in, the destructured props are taken from the ticket object knowledge and handed to the login. If the consumer exists, log within the consumer and ship the consumer knowledge again to the frontend app. If the consumer doesn’t exist, create new knowledge for the consumer and ship it again to the frontend app.

Now, we are able to transfer again to the consumer and create a Consumer part that may show the consumer particulars once they log in. We’ll create a part folder with a Consumer.js file and ship again the identify, e-mail, and picture.

We’ll use the useStore hook and render the displayed knowledge when a consumer logs in.

We will even verify that the Consumer is barely rendered when the information is distributed from the backend server and create a Logout button utilizing the GoogleAuth.signOut() API. When clicked, the button can be referred to as, take away the authData from the native storage, set the setAuthData to null, and reload the window:

import React from "react";
import { useStore } from "../hooks/useStore";
import { googleLogout } from "@react-oauth/google";
import "../Consumer.css";
const Consumer = () => {
 const { authData, setAuthData } = useStore();
 return (
   <div className={"container"}>
     {authData && (
       <>
         <h1>{authData.knowledge.identify}</h1>
         <p>{authData.knowledge.e-mail}</p>
         <img src={authData.knowledge.picture} alt="profile" />

         <button
           onClick={() => {
             googleLogout();
             localStorage.removeItem("AuthData");
             setAuthData(null);
             window.location.reload();
           }}
           className={"button"}
         >
           Logout
         </button>
       </>
     )}
   </div>
 );
};

export default Consumer;

Now, we are able to use Logout in App.js:

import "./App.css";
import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google";
import axios from "axios";
import { useStore } from "./hooks/useStore";
import Consumer from "./parts/Consumer";

perform App() {
 const setAuthData = useStore((state) => state.setAuthData);
 return (
   <div className="App">
     {!useStore((state) => state.authData) ? (
       <>
         <h1>Welcome</h1>
         <GoogleOAuthProvider
           clientId={course of.env.REACT_APP_GOOGLE_CLIENT_ID}
         >
           <GoogleLogin
             useOneTap={true}
             onSuccess={async (credentialResponse) => {
               console.log(credentialResponse);
               const { knowledge } = await axios.publish(
                 "http://localhost:8080/login",
                 {
                   // cross the token as a part of the req physique
                   token: credentialResponse.credential,
                 }
               );
               localStorage.setItem("AuthData", JSON.stringify(knowledge));
               setAuthData(knowledge);
             }}
             onError={() => {
               console.log("Login Failed");
             }}
           />
         </GoogleOAuthProvider>
       </>
     ) : (
       <>
         <h1>React x Nestjs Google Sign up</h1>
         <Consumer />
       </>
     )}
   </div>
 );
}

export default App;

Right here, we’ll verify if authData was despatched from the backend server with useStore hook. If there isn’t any knowledge, it can render a “Welcome” textual content and the Google login button. Nevertheless, if the consumer has logged in, we are going to render React x NestJs Google register and the Consumer Particulars.


Extra nice articles from LogRocket:


Now, let’s begin the entire course of and see it in motion:

Example of NestJS SSO Welcome in Google

Once we log in, now we have the textual content and the consumer particulars:

Example of NestJS Secure SSO

Subsequent, we are able to verify our database to see if the consumer is saved:

Checking the React-NestJS Database

Success! Our consumer is in our database. In abstract, we efficiently logged in utilizing Google sign-in, retrieved the consumer knowledge from NestJS, and displayed it on the React frontend app as soon as the consumer signed in.

Now, after we log off, we’ll see this:

NestJS SSO Log Out

Conclusion

On this tutorial, we discovered the right way to implement a safe Google sign-on in a NestJS challenge. We related it to a fundamental frontend React software whereas saving the consumer data in a database. You could find the entire code for this challenge in my repository right here. Blissful coding!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments