Validation middleware

We have to resort to a middleware when we cannot have automatic validation.

If you have noticed, we are not validating the login credentials in a DTO before we actually check if a user with that email exists and the password is correct. In this case, we unfortunately cannot use the @Body() decorator to validate the login credentials because guards are activated before pipes, and therefore the LocalAuthGuard is activated before the ValidationPipe. A solution we can use, although not as tidy as just using a DTO directly with the @Body() decorator, is to use a Middleware, which is the only thing that is activated before a guard. Let's then begin.

First, we will actually create a DTO for the login credentials. Create then, the file auth -> dto -> login.dto with the respective validation.

@IsEmail()
readonly email: string;

@IsPassword()
readonly password: string;

After that, let's create the middleware.

nest g mi auth/middleware/login-validation

Remember to replace the types of the parameters with Request, Response and NextFunction from express.

Inside the use() method, first we'll transform the req.body into an instance of the LoginDto.

const loginDto = plainToInstance(LoginDto, req.body);

Then, we'll manually use the validate() method from class-validator to obtain potential errors from the validation of the loginDto. Notice that we also use options to ensure that no strange fields will be accepted.

const errors = await validate(loginDto, {
  whitelist: true,
  forbidNonWhitelisted: true,
});

Then, if there are errors, we send their messages in a BadRequestException. We want to obtain only the values (Object.values()) from the constraints field, and then merge the resulting arrays into a single array with the error messages seperated by commas (join()).

if (errors.length) {
  const errorMessages = errors.map((error) =>
    Object.values(error.constraints).join(),
  );
  throw new BadRequestException(errorMessages);
}

Alright, we have our LoginValidationMiddleware. To use it, we need to go to the AuthModule. First, make it implement the NestModule interface. Then, inside the class, implement the configure() method, which receives a MiddlewareConsumer. Make the consumer call the apply() method with the middleware to be used. Then chain this with a call to the forRoutes() method, with the path to the route(s) that will receive the middleware.

export class AuthModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoginValidationMiddleware).forRoutes('auth/login');
  }
}

And there we have it, our fields are being validated.

Commit - Validating login credentials with middleware

Last updated