This post has some notes and the code I did while studying nestjs. I started those notes in my first post about nestjs with simple use of the components and a second post as a second step how to use the components. Now, I am going a step forward to use some security resources. More detail about it you can see in the nestjs page. The code used in this post you can find inside the messages-level-3 project.

The components will be shown here are:


Authentication

The Authentication is the guarantee you really are who you say you are. One way to make it safe is not only user and password but use token as well. It returns a token to be used in the next requests to validate the user.

Authentication (username/password) -> returning a JWTRequest -> JWT (token) -> protected route

# To install to use passport
$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local
#to use token
$ npm install --save @nestjs/jwt passport-jwt
$ npm install --save @types/passport-jwt

The token can helps to protect the available routes to an authenticated user. The process can be done using the @AuthGuard, creating a Strategy class to config the process to handle the token, and using the guard over the endpoint. The NestJS page shows the complete example to use it.

# PS: Code from NestJS page
# Strategy
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret,
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

# Module will use JWT  
@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: jwtConstants.secret,
      signOptions: { expiresIn: '60s' },
    }),
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}

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

# Controller
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
  return req.user;
}  

However, it's possible make it global to all endpoints. For that, it's necessary define on the main module, improve the guard and create a decorator to be used in the endpoints that should skip the authentication.

# PS Code from NestJS page
# Main module. Make it global.
{
   provide: APP_GUARD,
   useClass: JwtAuthGuard,
 },

# Decorator and constant
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

# Improve the guard
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) {
      return true;
    }
    return super.canActivate(context);
  }
}

# Add the decorator where should skip the authentication
@Public()
@Get()
findAll() {
  return [];
}

Authorization

Authorization refers to the process that determines what a user is able to do.

The authorization is regarding define roles to filter what can be access or not. The complete example you can see in the NestJS page.


Encryption and Hashing

Encryption is the process of encoding information. Hashing is the process of converting a given key into another value. [1]

One example is signup a new user. The codes to create the new password are below.

Here is the first example using a random series of numbers and letters, salt, to improve the security. After generate that number, the password and salt are concatenate to create the hash number. The last step is concatenate the result and the original salt value. It will make available to validate the password.

# auth.util.ts
public async buildPassword(password: string) {
   // hash the user pws
   //Generate Salt
   const salt = randomBytes(8).toString('hex');

   //Hash the salt and pwd
   const hash = (await scrypt(password, salt, 32)) as Buffer;

   // Join the hashed result and salt
   const result = salt + '.' + hash.toString('hex');

   return result;
 }

# Request
POST localhost:3001/auth/v1/signup
{
 "email": "myemail@example.com",
 "password": "123"
}

# Response
{
 "id": 1,
 "email": "myemail@example.com",
 "password": "84c8d22ec8631fd0.669f933ae6f058c053831fa4770687f813f1b973e1b86c3067a74605894d0d71"
}

A second version is using exactly what nestjs documentation show.

# auth.util.js
public async buildPasswordV2(password: string) {
  const iv = randomBytes(16);
  const word = 'Word used to generate key';
  const key = (await promisify(scrypt)(word, 'salt', 32)) as Buffer;
  const cipher = createCipheriv('aes-256-ctr', key, iv);

  const encryptedText = Buffer.concat([
    cipher.update(password),
    cipher.final(),
  ]);

  return encryptedText.toString();
}

# Request
POST localhost:3001/auth/v2/signup
{
 "email": "myemail@example.com",
 "password": "123kjhkjhyutu"
}

# Response
{
  "id": 1,
  "email": "myemail@example.com",
  "password": "\r@o\u0013�g�%g���"
}

A third version is using what nestjs documentation show with hash using the library bcrypt.

public async buildPasswordV3(password: string) {
  const salt = await bcrypt.genSalt();
  return await bcrypt.hash(password, salt);
}

public async isPwdValidV3(password: string, userPassword: string) {
  return await bcrypt.compare(password, userPassword);
}

Helmet

Helmet can help protect your app from some well-known web vulnerabilities by setting HTTP headers appropriately.

# Install
$ npm i --save helmet

# main.ts
 app.use(helmet);

CORS

Cross-origin resource sharing (CORS) is a mechanism that allows resources to be requested from another domain. Under the hood, Nest makes use of the Express cors package.

# main.ts
app.enableCors();

CSRF Protection

Cross-Site Request Forgery: unauthorized command from a trusted user. A good way to prevent this kind of attack is using tokens.

Nestjs has the package csurf to mitigate this risk. It needs session middleware or cookie-parser.

# install
$ npm i --save csurf

# main.js
import * as csurf from 'csurf';

app.use(csurf());

Rate limiting

The rate-limitingis a common technique to protect applications from brute-force attacks.

In the example, regarding the ThrottlerModule, the 'ttl' attribute is the time to live and the 'limit' is the quantity of request to the 'ttl'. Also, you see the guard used to handle that. Here it is global, but not mandatory. There is possibility, for example, to customize it or use decorators.

# install
$ npm i --save @nestjs/throttler

# MessageModule
@Module({
  imports: [
    AuthModule,
    ConfigModule.register({ attribute: 'staging' }),
    ThrottlerModule.forRoot({
      //time to live
      ttl: 60,
      limit: 10,
    }),
  ],
  providers: [
  MessagesService,
  MessagesRepository,
  { provide: APP_GUARD, useClass: ThrottlerGuard },
],
  controllers: [MessagesController],
})
export class MessagesModule {