10. Implementing JWT Authentication
Published: 2023-11-07 03:27:43

## Introduction

In our previous tutorial, we set up local authentication in a Nest.js application to handle user login and authentication. Now, we'll take it a step further and implement JWT (JSON Web Token) authentication to secure routes and endpoints in our application.

## Creating the JWT Strategy

**Create JWT Strategy:** First, we need to create a JWT strategy that validates JWT tokens. Inside your auth/strategies folder, create a new TypeScript file, jwt.strategy.ts, and define the JWT strategy:
run the command to create `jwt.strategy.ts `

```
touch apps/auth/src/strategies/jwt.strategy.ts
```

populate it with the following content 

```
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { UsersService } from '../users/users.service';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import { Tokenpayload } from '../interface/token-payload.interface';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    private readonly configService: ConfigService,
    private readonly userService: UsersService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromExtractors([
        (request: Request) => request.cookies.Authentication, // JWT in a cookie
      ]),
      secretOrKey: configService.get('JWT_SECRET'),
    });
  }

  async validate({ userId }: Tokenpayload) {
    return this.userService.getUser({ _id: userId });  //We will define 'getUser' in the user service later; for now, let's just declare it
  }
}

```

## Creating DTO for getUser
To create `get-user.dto.ts` inside our user/dto folder run this command on your terminal:

```
touch apps/auth/src/users/dto/get-user.dto.ts
```

populate it with the following content:

```
import { IsNotEmpty, IsString } from 'class-validator';

export class GetUserDto {
  @IsString()
  @IsNotEmpty()
  _id: string;
}

```

In this DTO, we use the @IsEmail decorator to validate that the email field has a valid email format. This DTO is intended for retrieving a user by their email, and it enforces email format validation.

---

## Implementing getUser method in user service

open your user.service.ts and update the content with the following code:

```
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { UserRepository } from './user.repository';
import { CreateUserDTO } from './dto/create-user.dto';
import * as bcryptjs from 'bcryptjs';
import { GetUserDto } from './dto/get-user.dto';
@Injectable()
export class UsersService {
  constructor(private readonly userRepository: UserRepository) {}

  async create(createUserDto: CreateUserDTO) {
    return this.userRepository.create({
      ...createUserDto,
      password: await bcryptjs.hash(createUserDto.password, 10),
    });
  }

  async verifyUser(email: string, password: string) {
    const user = await this.userRepository.findOne({ email });
    const passwordIsValid = await bcryptjs.compare(password, user.password);
    if (!passwordIsValid) {
      throw new UnauthorizedException('Invalid credentials');
    }
    return user;
  }

  async getUser(getUserDto: GetUserDto) {
    return this.userRepository.findOne(getUserDto);
  }
}

```
we just added a getUser method 

---

## Creating the JWT Auth Guard

**Create JWT Auth Guard:** Similar to our local authentication guard, we need an auth guard for JWT. Create a jwt-auth.guard.ts file in your guards folder to do this run this command on your terminal:

```
touch apps/auth/src/guards/jwt-auth.guard.ts
```

populate it with the following content:

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

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

```

---

## Using JWT Authentication

**Use JWT Auth Guard:** You can use the JwtAuthGuard in your controller methods or routes to protect them.

**Apply JWT Authentication:** In your controllers, you can apply JWT authentication using the @UseGuards decorator. to protect routes: For example, to protect a route that returns the current update your `user.controller.ts` content with the following code:

```
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDTO } from './dto/create-user.dto';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { CurrentUser } from '../current-user.decorator';
import { UserDocument } from './models/user.schema';

@Controller('users')
export class UsersController {
  constructor(private readonly userService: UsersService) {}

  @Post()
  async createUser(@Body() createUserDto: CreateUserDTO) {
    return this.userService.create(createUserDto);
  }

  @Get()
  @UseGuards(JwtAuthGuard)
  getCurrentUser(@CurrentUser() user: UserDocument) {
    return user;
  }
}

```

> In the auth module, add the jwt strategy as a provider:
update your auth.module.ts with the following content

```
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UsersModule } from './users/users.module';
import { LoggerModule } from '@app/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as Joi from 'joi';
import { LocalStrategy } from './strategies/local.strategy';
import { JwtStrategy } from './strategies/jwt.strategy';

@Module({
  imports: [
    UsersModule,
    LoggerModule,
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        JWT_SECRET: Joi.string().required(),
        JWT_EXPIRATION: Joi.string().required(),
        PORT: Joi.number().required(), //we wil setup this env later
      }),
    }),
    JwtModule.registerAsync({
      useFactory: (configService: ConfigService) => ({
        secret: configService.get<string>('JWT_SECRET'),
        signOptions: {
          expiresIn: `${configService.get<string>('JWT_EXPIRATION')}s`,
        },
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, LocalStrategy, JwtStrategy],
})
export class AuthModule {}

```

---

## Add cookie-parser Middleware
install cookie-parser run the following command on your terminal 

```
npm install cookie-parser
npm install -D @types/cookie-parser
```

open your auth/src/main.ts file and update it with the following content to use cookie-parser as middleware 

```
import { NestFactory } from '@nestjs/core';
import { AuthModule } from './auth.module';
import { ValidationPipe } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
import { ConfigService } from '@nestjs/config';
import * as cookieParser from 'cookie-parser';

async function bootstrap() {
  const app = await NestFactory.create(AuthModule);
  app.use(cookieParser());
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
    }),
  );
  app.useLogger(app.get(Logger));
  const configService = app.get(ConfigService);
  await app.listen(configService.get('PORT'));
}
bootstrap();

```

---

## Validating Unique Email during User Creation

**Ensure Unique Email:** To prevent the creation of multiple users with the same email, you can add validation during user creation. Modify your user service to validate email uniqueness before creating a new user: update your **user.service.ts** with the following content:

```
import {
  Injectable,
  UnauthorizedException,
  UnprocessableEntityException,
} from '@nestjs/common';
import { UserRepository } from './user.repository';
import { CreateUserDTO } from './dto/create-user.dto';
import * as bcryptjs from 'bcryptjs';
import { GetUserDto } from './dto/get-user.dto';
@Injectable()
export class UsersService {
  constructor(private readonly userRepository: UserRepository) {}

  async create(createUserDto: CreateUserDTO) {
    await this.validateCreateUserDto(createUserDto);
    return this.userRepository.create({
      ...createUserDto,
      password: await bcryptjs.hash(createUserDto.password, 10),
    });
  }

  private async validateCreateUserDto(createUserDto: CreateUserDTO) {
    try {
      await this.userRepository.findOne({ email: createUserDto.email });
    } catch (error) {
      return;
    }
    throw new UnprocessableEntityException('Email already exists');
  }

  async verifyUser(email: string, password: string) {
    const user = await this.userRepository.findOne({ email });
    const passwordIsValid = await bcryptjs.compare(password, user.password);
    if (!passwordIsValid) {
      throw new UnauthorizedException('Invalid credentials');
    }
    return user;
  }

  async getUser(getUserDto: GetUserDto) {
    return this.userRepository.findOne(getUserDto);
  }
}

```
We have just added a 'validateCreateUserDto' function to verify the existence of an email in our database, ensuring email uniqueness.

---

With these steps, you've implemented JWT authentication in your Nest.js application, ensuring secure access to protected routes. Additionally, you've added email uniqueness validation during user creation to maintain data integrity in your user database.

In the next tutorial, we'll explore more advanced authentication features and user management.

Stay tuned for the next part of our Nest.js authentication series!