NestJS, a progressive Node.js framework, offers robust capabilities for implementing authentication and authorization in web applications. This essay examines the process of integrating these crucial security features using NestJS, focusing on user management, secure password handling, and JSON Web Token (JWT) implementation.

The process begins with the generation of an Auth module, complete with associated controller and service components. This modular approach aligns with NestJS’s architectural principles, promoting code organization and maintainability. The Auth module is then integrated into the application’s main module, establishing a foundation for user-related functionalities.

User management is implemented through a dedicated Users entity, which includes fields for unique identifiers, usernames, passwords, and creation timestamps. This entity is managed via a UsersRepository, which extends TypeORM’s Repository class, providing an abstraction layer for database operations.

A critical aspect of user authentication is secure password handling. The implementation utilizes bcrypt, a well-regarded hashing function, to generate salted and hashed passwords before storage. This approach significantly enhances security by ensuring that plain-text passwords are never stored in the database.

The authentication flow comprises two main operations: user sign-up and sign-in. The sign-up process involves creating new user records with validated credentials, while sign-in verifies provided credentials against stored data. Both operations are exposed through the AuthController, which delegates to the AuthService for business logic execution.

Input validation is enforced using class-validator decorators, ensuring that usernames and passwords meet specified criteria. This includes length constraints and complexity requirements for passwords, thereby enhancing the overall security posture of the application.

The implementation of JSON Web Tokens (JWT) marks a significant step in the authorization process. JWTs are generated upon successful authentication and serve as secure, stateless session management tools. The JwtModule is configured with a secret key and token expiration time, providing a balance between security and user experience.

A custom JWTStrategy is implemented, extending Passport’s strategy pattern. This strategy is responsible for JWT validation and user retrieval based on the token payload. The integration of Passport and JWT in NestJS creates a seamless authentication flow, where valid tokens grant access to protected resources.

The essay concludes by discussing the integration of JWT-based authentication with the application’s task management feature. By applying the AuthGuard to the TasksController, all task-related endpoints are secured, ensuring that only authenticated users can access these resources.

In conclusion, this comprehensive approach to authentication and authorization in NestJS demonstrates the framework’s flexibility and power in building secure web applications. By leveraging TypeORM for data persistence, Passport for authentication strategies, and JWT for stateless authorization, developers can create robust, scalable systems that prioritize security without compromising on functionality or performance.

Generate Auth module controller and service

text
nest g module authnest g service auth --no-specnest g controller auth --no-specyarn start:dev

Source Code :

Initial Auth Folder

app.module.ts

typescript
@Module({
  imports: [...TasksModule, AuthModule,],
})

auth.module.ts

typescript
import {
  Module
}
from '@nestjs/common';
import {
  AuthService
}
from './auth.service';
import {
  AuthController
}
from './auth.controller';
import {
  TypeOrmModule
}
from '@nestjs/typeorm';
import {
  UsersRepository
}
from './users.repository';
@Module({
  imports: [TypeOrmModule.forFeature([Users])], providers: [AuthService, UsersRepository],
  controllers: [AuthController],
})
export class AuthModule {
}

auth.controller.ts

typescript
import {
  Controller
}
from '@nestjs/common';
@Controller('auth')
export class AuthController {
}

auth.service.ts

typescript
import {
  Injectable
}
from '@nestjs/common';
import {
  UsersRepository
}
from './users.repository';
import {
  InjectRepository
}
from '@nestjs/typeorm';
@Injectable()
export class AuthService {
  constructor(@InjectRepository(UsersRepository) private userRepository: UsersRepository,) {
  }
}

auth.repository.ts

typescript
import {
  Injectable
}
from '@nestjs/common';
import {
  DataSource, Repository
}
from 'typeorm';
import {
  Users
}
from './user.entity';
@Injectable()
export class UsersRepository extends Repository<Users> {
  constructor(private ds: DataSource) {
    super(Users, ds.createEntityManager());
  }
}

auth.entity.ts

typescript
import {
  Column, Entity, PrimaryGeneratedColumn
}
from 'typeorm';
@Entity()
export class Users {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column() username: string;
  @Column() password: string;
  @Column() created_at: Date;
}
Authentication and Authorization in NestJS : A Comprehensive Overview

Create User or signup

add migration sql

sql
-- liquibase formatted sql
-- changeset mobin:2
CREATE TABLE IF NOT EXISTS "users" (
    "id" UUID NOT NULL DEFAULT gen_random_uuid(),
    "username" TEXT NOT NULL,
    "password" TEXT NOT NULL,
    "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY ("id")
);

CREATE INDEX ON "users" ("id");

-- rollback DROP TABLE "tasks";

apply update

bash
sudo
docker-compose -f docker-compose.yml exec liquibase /bin/shliquibase update
--url=jdbc:postgresql://postgres-tasks:5432/task?currentSchema=public --changelog-file=changelog.xml
--username=user --password=pass
Authentication and Authorization in NestJS : A Comprehensive Overview

adminer

text
http://127.0.0.1:10000/?pgsql=postgres-tasks&username=user&db=task&ns=public&table=users
Authentication and Authorization in NestJS : A Comprehensive Overview

postman

Authentication and Authorization in NestJS : A Comprehensive Overview
Authentication and Authorization in NestJS : A Comprehensive Overview

dto

typescript
export class AuthCredentialsDto {
  username: string;
  password: string;
}

user.repository.ts

typescript
import {
  Injectable
}
from '@nestjs/common';
import {
  DataSource, Repository
}
from 'typeorm';
import {
  Users
}
from './user.entity';
import {
  AuthCredentialsDto
}
from './dto/auth-credentials.dto';
@Injectable()
export class UsersRepository extends Repository<Users> {
  constructor(private ds: DataSource) {
    super(Users, ds.createEntityManager());
  }
  async createUser(authCredentialDto: AuthCredentialsDto): Promise<void> {
    const {
      username, password
    }
    = authCredentialDto;
    const user = this.create({
      username, password,
    });
    await this.save(user);
  }
}

auth.service.ts

typescript
import {
  Injectable
}
from '@nestjs/common';
import {
  UsersRepository
}
from './users.repository';
import {
  InjectRepository
}
from '@nestjs/typeorm';
import {
  AuthCredentialsDto
}
from './dto/auth-credentials.dto';
@Injectable()
export class AuthService {
  constructor(@InjectRepository(UsersRepository) private userRepository: UsersRepository,) {
  }
  async signUp(authCredentialsDto: AuthCredentialsDto): Promise<void> {
    return this.userRepository.createUser(authCredentialsDto);
  }
}

auth.controller.ts

typescript
import {
  Body, Controller, Post
}
from '@nestjs/common';
import {
  AuthService
}
from './auth.service';
import {
  AuthCredentialsDto
}
from './dto/auth-credentials.dto';
@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {
  }
  @Post('/signup') async signUp(@Body() authCredentialsDto: AuthCredentialsDto): Promise<void> {
    return this.authService.signUp(authCredentialsDto);
  }
}

User Validation

typescript
import {
  IsString, Matches, MaxLength, MinLength
}
from 'class-validator';
export class AuthCredentialsDto {
  @IsString() @MinLength(4) @MaxLength(20) username: string;
  @IsString() @MinLength(8) @MaxLength(32)
  @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
    message: 'Password too weak',
  }) password: string;
}

This regular expression is typically used for password validation. Let’s break it down step by step:

1. `/` at the start and end: These delimit the regular expression.

2. `((?=.*\d)|(?=.*\W+))`: This is a positive lookahead that ensures the string contains either:
 — `(?=.*\d)`: At least one digit
 — OR
 — `(?=.*\W+)`: At least one non-word character (special character)

3. `(?![.\n])`: This is a negative lookahead that ensures the string doesn’t end with a dot or newline.

4. `(?=.*[A-Z])`: This is a positive lookahead that ensures the string contains at least one uppercase letter.

5. `(?=.*[a-z])`: This is a positive lookahead that ensures the string contains at least one lowercase letter.

6. `.*`: This matches any character (except newline) zero or more times.

7. `$`: This asserts the position at the end of the string.

In summary, this regular expression validates a string that meets the following criteria:
- Contains at least one digit or special character
- Does not end with a dot or newline
- Contains at least one uppercase letter
- Contains at least one lowercase letter
- Can contain any characters, and must be at the end of the string

This type of regex is commonly used for ensuring password strength, requiring a mix of character types for security.

Prevent repeated username

entity

typescript
import {
  Column, Entity, PrimaryGeneratedColumn
}
from 'typeorm';
@Entity()
export class Users {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({
    unique: true
  }) username: string;
  @Column() password: string;
  @Column() created_at: Date;
}

repository

typescript
async createUser(authCredentialDto: AuthCredentialsDto): Promise<void> {    const { username,
password } = authCredentialDto;    const user = this.create({      username,      password,    });
try {      await this.save(user);    } catch (error) {      console.error(error);      if
(error.code === '23505') {        throw new ConflictException('Username already exists');      }
else {        throw new InternalServerErrorException();      }    }  }

Using Bcrypt for store hash of password

bash
yarn add bcrypt
Authentication and Authorization in NestJS : A Comprehensive Overview

repository

typescript
async createUser(authCredentialDto: AuthCredentialsDto): Promise<void> {    const { username,
password } = authCredentialDto;    const salt = await bycrypt.genSalt();    const hashedPassword =
await bycrypt.hash(password, salt);    const user = this.create({      username,      password:
hashedPassword,    });    try {      await this.save(user);    } catch (error) {
console.error(error);      if (error.code === '23505') {        throw new
ConflictException('Username already exists');      } else {        throw new
InternalServerErrorException();      }    }  }

Sign in

controller

typescript
@Post('/signin') async signIn(@Body() authCredentialsDto: AuthCredentialsDto,): Promise<string> {
  return this.authService.signIn(authCredentialsDto);
}

service

typescript
async signIn(authCredentialsDto: AuthCredentialsDto): Promise<string> {    return
this.userRepository.validateUserPassword(authCredentialsDto);  }

repository

typescript
async validateUserPassword(    authCredentialDto: AuthCredentialsDto,  ): Promise<string> {    const
{ username, password } = authCredentialDto;    const user = await this.findOne({ where: { username }
});    if (user && (await bycrypt.compare(password, user.password))) {      return 'success';    }
else {      return 'Invalid credentials';    }  }

JWT

install

bash
yarn add @nestjs/jwt @nestjs/passport passport passport-jwt

auth.model

typescript
import {
  Module
}
from '@nestjs/common';
import {
  AuthService
}
from './auth.service';
import {
  AuthController
}
from './auth.controller';
import {
  TypeOrmModule
}
from '@nestjs/typeorm';
import {
  UsersRepository
}
from './users.repository';
import {
  Users
}
from './user.entity';
import {
  PassportModule
}
from '@nestjs/passport';
import {
  JwtModule
}
from '@nestjs/jwt';
@Module({
  imports: [PassportModule.register({
    defaultStrategy: 'jwt'
  }), JwtModule.register({
    secret: 'topSecret51', signOptions: {
      expiresIn: 3600,
    },
  }), TypeOrmModule.forFeature([Users]),], providers: [AuthService, UsersRepository], controllers:
  [AuthController],
})
export class AuthModule {
}

jwt-payload.interface.ts

typescript
export interface JwtPayload {
  username: string;
}

controller

typescript
@Post('/signin') async signIn(@Body() authCredentialsDto: AuthCredentialsDto,): Promise<{
  accessToken: string
}
> {
  return this.authService.signIn(authCredentialsDto);
}

service

typescript
import {
  Injectable
}
from '@nestjs/common';
import {
  UsersRepository
}
from './users.repository';
import {
  InjectRepository
}
from '@nestjs/typeorm';
import {
  AuthCredentialsDto
}
from './dto/auth-credentials.dto';
import {
  JwtService
}
from '@nestjs/jwt';
import {
  JwtPayload
}
from './jwt-payload.intreface';
@Injectable()
export class AuthService {
  constructor(@InjectRepository(UsersRepository) private userRepository: UsersRepository, private
  jwtService: JwtService,) {
  }
  async signUp(authCredentialsDto: AuthCredentialsDto): Promise<void> {
    return this.userRepository.createUser(authCredentialsDto);
  }
  async signIn(authCredentialsDto: AuthCredentialsDto,): Promise<{
    accessToken: string
  }
  > {
    if (await this.userRepository.validateUserPassword(authCredentialsDto)) {
      const payload: JwtPayload = {
        username: authCredentialsDto.username
      };
      const accessToken: string = this.jwtService.sign(payload);
      return {
        accessToken
      };
    }
  }
}

repository

javascript
async validateUserPassword(    authCredentialDto: AuthCredentialsDto,  ): Promise<boolean> {
const { username, password } = authCredentialDto;    const user = await this.findOne({ where: {
username } });    if (user && (await bycrypt.compare(password, user.password))) {      return true;
} else {      return false;    }  }

JWT Validation

Install package

bash
yarn add @types/passport-jwt

auth.module.ts

typescript
import {
  Module
}
from '@nestjs/common';
import {
  AuthService
}
from './auth.service';
import {
  AuthController
}
from './auth.controller';
import {
  TypeOrmModule
}
from '@nestjs/typeorm';
import {
  UsersRepository
}
from './users.repository';
import {
  Users
}
from './user.entity';
import {
  PassportModule
}
from '@nestjs/passport';
import {
  JwtModule
}
from '@nestjs/jwt';
import {
  JWTStrategy
}
from './jwt.strategy';
@Module({
  imports: [PassportModule.register({
    defaultStrategy: 'jwt'
  }), JwtModule.register({
    secret: 'topSecret51', signOptions: {
      expiresIn: 3600,
    },
  }), TypeOrmModule.forFeature([Users]),], providers: [AuthService, UsersRepository, JWTStrategy],
  controllers: [AuthController], exports: [JWTStrategy, PassportModule],
})
export class AuthModule {
}

jwt.strategy.ts

typescript
import {
  Injectable, UnauthorizedException
}
from '@nestjs/common';
import {
  PassportStrategy
}
from '@nestjs/passport';
import {
  ExtractJwt, Strategy
}
from 'passport-jwt';
import {
  UsersRepository
}
from './users.repository';
import {
  InjectRepository
}
from '@nestjs/typeorm';
import {
  JwtPayload
}
from './jwt-payload.intreface';
import {
  Users
}
from './user.entity';
@Injectable()
export class JWTStrategy extends PassportStrategy(Strategy) {
  constructor(@InjectRepository(UsersRepository) private userRepository: UsersRepository,) {
    super({
      secretOrKey: 'topSecret51', jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    });
  }
  async validate(payload: JwtPayload): Promise<Users> {
    const {
      username
    }
    = payload;
    const user: Users = await this.userRepository.findOne({
      where: {
        username
      },
    });
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

Add JWT to tasks

tasks.module.ts

typescript
@Module({
  imports: [TypeOrmModule.forFeature([Tasks]), AuthModule], controllers: [TasksController],
  providers: [TasksService, TaskRepository],
})

tasks.controller.ts

typescript
@Controller('tasks')@UseGuards(AuthGuard())

add jwt token to bearer

Authentication and Authorization in NestJS : A Comprehensive Overview