Strengthen Your Website Security – NestJS Documentation Update

Strengthen Your Website Security – NestJS Documentation Update
In the latest NestJS documentation, the author has separated security into its own chapter to emphasize the importance of web security. Following the official documentation’s approach can effectively improve your site’s security.
tags: nestjs, authentication, authorization, helmet, cors, csrf, encryption, hashing
Authentication
Authentication is a crucial part of most existing applications. There are many different techniques, strategies, and approaches to handling user authorization. The approach any project adopts depends on its specific application requirements. This chapter introduces several authentication methods that can be adapted to a wide variety of needs.
Passport is currently the most popular authentication library for Node.js, well known in the community and used in many production applications. Integrating this tool with the Nest framework is very simple. As a demonstration, we will set up the passport-http-bearer and passport-jwt strategies.
Passport is the most popular Node.js authentication library, widely recognized in the community and successfully used in many production applications. Integrating this library into a Nest application using the @nestjs/passport module is straightforward. At a high level, Passport performs a series of steps to:
- verify a user’s identity by validating their “credentials” (for example, username/password, JSON Web Token (JWT), or identity token from an identity provider);
- manage authenticated state (by issuing portable tokens such as JWTs, or by creating an Express session);
- attach information about the authenticated user to the request object for further use in route handlers.
Passport has a rich ecosystem of strategies that implement various authentication mechanisms. Although the concept is simple, there is a large, diverse set of Passport strategies you can choose from. Passport abstracts the different steps into a standard pattern, and the @nestjs/passport module wraps and standardizes that pattern into familiar Nest constructs.
In this chapter, we’ll use these powerful and flexible modules to implement a complete end-to-end authentication solution for a RESTful API server. You can use the concepts described here to implement Passport strategies and tailor your authentication scheme. You can follow the steps in this chapter to build the complete example. You can find a repository containing the full example application there.
Authentication
Let’s flesh out our requirements. In this use case, the client will first authenticate using a username and password. Once authenticated, the server will issue a JWT, which can be sent in the Authorization header as a token on subsequent requests to verify authentication. We will also create a protected route that is only accessible to requests containing a valid JWT.
We’ll start with the first requirement: verifying the user. Then we’ll extend it by issuing JWTs. Finally, we’ll create a protected route that checks for a valid JWT in the request.
First, we need to install the required packages. Passport provides a strategy called passport-local, which implements a username/password authentication mechanism that fits our use case in this section.
$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local
For any Passport strategy you choose, you’ll need the @nestjs/passport and passport packages. Then you must install the package for the specific strategy (for example, passport-jwt or passport-local), which implements the particular authentication strategy you’re building. In addition, you can install type definitions for any Passport strategy, as shown above with @types/passport-local, which helps when writing TypeScript code.
Passport Strategies
We can now implement the authentication functionality. We’ll first outline the flow used by any Passport strategy. It’s helpful to think of Passport itself as a framework. The elegance of the framework is that it abstracts the authentication process into a few basic steps that you customize according to the strategy you implement. It’s similar to a framework in that you configure it by providing custom parameters (as a JSON object) and callback functions (which Passport calls at the appropriate time). The @nestjs/passport module wraps this framework in a Nest-style package, making it easy to integrate into a Nest application. Below, we’ll use @nestjs/passport, but first let’s consider how vanilla Passport works.
In vanilla Passport, you configure a strategy by providing these two things:
- Options specific to that strategy. For example, in the JWT strategy, you can provide a secret for signing tokens.
- A verify callback where you tell Passport how to interact with your user store (where you manage user accounts). Here you check whether the user exists (or create a new one), and whether their credentials are valid. The Passport library expects this callback to return the full user object if verification succeeds, or
nullif it fails (failure means the user wasn’t found, or in the case ofpassport-local, the password doesn’t match).
With @nestjs/passport, you configure Passport strategies by extending the PassportStrategy class. You optionally pass an options object (item 1 above) to the super() call in the subclass constructor. You supply the verify callback (item 2 above) by implementing the validate() method in your subclass.
We’ll start by generating an AuthModule with an AuthService:
$ nest g module auth
$ nest g service auth
When we implement AuthService, we’ll find it useful to encapsulate user operations in a UsersService, so let’s generate this module and service now:
$ nest g module users
$ nest g service users
Replace the default content of the generated files as follows. For our sample application, UsersService simply maintains an in-memory hard-coded list of users and provides a find method to retrieve users by username. In a real application, this is where you would build your user model and persistence layer using your chosen library (e.g., TypeORM, Sequelize, Mongoose, etc.).
users/users.service.ts
import { Injectable } from '@nestjs/common';
export type User = any;
@Injectable()
export class UsersService {
private readonly users: User[];
constructor() {
this.users = [
{
userId: 1,
username: 'john',
password: 'changeme',
},
{
userId: 2,
username: 'chris',
password: 'secret',
},
{
userId: 3,
username: 'maria',
password: 'guess',
},
];
}
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
In UsersModule, the only change we need is to add UsersService to the exports array of the @Module decorator so that it’s visible to other modules (we’ll soon use it in AuthService).
users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
Our AuthService is responsible for retrieving users and validating passwords. To do this, we create a validateUser() method. In the code below, we use the ES6 spread operator to strip the password property from the user object before returning it. Later, we’ll call validateUser() from the Passport local strategy.
auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(private readonly usersService: UsersService) {}
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.usersService.findOne(username);
if (user && user.password === pass) {
const { password, ...result } = user;
return result;
}
return null;
}
}
Of course, in a real application, you would not store passwords in plain text. Instead, you would use a library like bcrypt with a one-way cryptographic hash algorithm. With this approach, you only store hashed passwords, and you compare the stored hash with the hash of the incoming password, so user passwords are never stored or exposed in plain text. To keep our sample application simple, we violate this absolute rule and use plain text. Do not do this in a real application!
Now we update AuthModule to import UsersModule.
auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule],
providers: [AuthService],
})
export class AuthModule {}
Now we can implement the Passport local authentication strategy. Create a file named local.strategy.ts in the auth folder and add the following code:
auth/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
We’ve followed all the Passport strategy steps described earlier. In our passport-local use case, there are no configuration options, so our constructor simply calls super() with no options object.
We’ve also implemented the validate() method. For each strategy, Passport calls the verify function (implemented as validate() in @nestjs/passport) with an appropriate, strategy-specific set of parameters. For the local strategy, Passport expects a validate() method with the following signature:
validate(username: string, password: string): any.
Most of the validation work is done in our AuthService (with help from UsersService), so this method is quite simple. The validate() method of any Passport strategy will follow a similar pattern; the only difference is the specific credential details. If a user is found and the credentials are valid, we return that user so Passport can complete its job (for example, attaching a user property to the request object), and the request handling pipeline can continue. If not, we throw an exception and let the exception layer handle it.
Typically, the only significant difference between each strategy’s validate() method is how you determine whether the user exists and is valid. For example, in a JWT strategy, depending on your requirements, you might check whether the userId carried in the decoded token matches a record in your user database, or whether it appears in a list of revoked tokens. This pattern of subclassing and implementing strategy-specific validation is consistent, elegant, and extensible.
We need to configure AuthModule to use the Passport features we just defined. Update auth.module.ts so it looks like this:
auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [UsersModule, PassportModule],
providers: [AuthService, LocalStrategy],
})
export class AuthModule {}
Built-in Passport Guards
The chapter on guards describes their main function: determining whether a request will be handled by the route handler. This is still true, and we’ll soon use this standard feature. However, when using the @nestjs/passport module, we’ll introduce a slightly different aspect of guards that may be confusing at first, so let’s discuss it now.
From an authentication perspective, your application can exist in two states:
- The user/client is not logged in (unauthenticated)
- The user/client is logged in (authenticated)
In the first case (the user is not logged in), we need to perform two different functions:
- Restrict the routes that unauthenticated users can access (i.e., deny access to restricted routes).
We’ll use familiar guards to handle this by placing a guard on protected routes. In this guard, we’ll check for a valid JWT, so we’ll address this after we successfully issue a JWT. - Initiate the authentication flow when a previously unauthenticated user attempts to log in.
This is when we issue a JWT to a valid user. Given this requirement, we know we need toPOSTusername/password credentials to start the authentication process, so we’ll set up aPOST /auth/loginroute to handle this. This raises a question: how exactly do we apply thepassport-localstrategy on this route?
The answer is simple: by using another, slightly different type of guard.
The @nestjs/passport module provides a built-in guard for us to accomplish this. This guard calls the Passport strategy and initiates the steps described above (retrieving credentials, running the verify function, creating the user property, and so on). The second case mentioned above (logged-in user) only relies on the standard type of guard we’ve already discussed, in order to allow authenticated users to access protected routes.
Login Route
With the strategy in place, we can now implement a simple /auth/login route and apply the built-in guard to start the Passport local flow.
Open the app.controller.ts file and replace its contents with the following:
app.controller.ts
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller()
export class AppController {
@UseGuards(AuthGuard('local'))
@Post('auth/login')
async login(@Request() req) {
return req.user;
}
}
For @UseGuards(AuthGuard('local')), we’re using an AuthGuard that @nestjs/passport automatically prepares for us when we extend the passport-local strategy. Let’s break this down. Our Passport local strategy is named "local" by default. We reference this name in the @UseGuards() decorator so that it is associated with the code provided by the passport-local package. This resolves ambiguity when you have multiple Passport strategies in your application (each strategy may provide a strategy-specific AuthGuard). Although we currently only have one strategy, we’ll soon add a second, so this disambiguation is necessary.
To test our route, we simply return the user from the /auth/login route. This also lets us demonstrate another Passport feature: Passport automatically creates a user object on the request based on what is returned from the validate() method, and assigns it to req.user. Later, we’ll replace this with code that creates and returns a JWT.
Since these are API routes, we’ll use the common curl tool to test them. You can test with any of the hard-coded user objects from UsersService.
# POST to /auth/login
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "john", "password": "changeme"}'
If the credentials are correct, you should see the corresponding user object returned, without the password field.


