For authentication, Nest uses an established library—Passport—and integrates it into the framework via an abstraction layer.
The flexibility of Passport allows you to implement different logon strategies. In our case, we use JSON web tokens (JWTs).
Setup
First you need to install some additional packages via the npm install @nestjs/passport passport @nestjs/jwt passport-jwt command. In addition, you must install the type definitions of passport-jwt as DevDependency via the npm install --save-dev @types/passport-jwt command.
Theoretically, you can store your users’ credentials permanently in the source code, but because there’s already an existing database connection, it makes sense to use this database to store user data as well. Therefore, you should first create a new module named auth using the nest generate module auth command. Within this module, you then create a user entity using the nest generate class user.entity auth command. Then you modify this class in such a way that it represents a valid entity and reflects the structure of the database table. This listing shows what this class should look like.
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('User')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
}
After that, you include the entity in the import of the TypeORM module within the app and auth modules in the entities array, just as you did with the movie entity. The next listing contains the current state of the auth module.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
@Module({ imports: [TypeOrmModule.forFeature([User])] })
export class AuthModule {}
Authentication Service
In the next step, you implement the central service that takes care of authentication issues such as verifying a user and generating a JWT. You can create this service via the nest generate service auth command. In the service class, you implement the validateUser and login methods, as you can see here.
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
private jwtService: JwtService,
) {}
async validateUser(username: string, password: string): Promise<Omit<User,
'password'>> {
const user = await this.userRepository.findOne({username, password});
if (user) {
const validatedUser = {...user};
delete validatedUser.password;
return validatedUser;
}
return null;
}
async login(user: User) {
const payload = { username: user.username, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
}
In the constructor, you have Nest create an instance of the user repository and the JWT service. The validateUser method receives the user name and password entered at login as arguments. These two pieces of information are used to search for the matching data record in the database. If this search returns a hit, you delete the password from the received data record and return it. If the information was invalid, that is, no matching record was found, you return the value null.
The login method receives a user object as an argument and returns an object with a signed JWT. This token contains the user name and the ID of the user. At this point you must make sure not to include passwords or similar critical data in a token under any circumstances.
Login Controller: Endpoint for User Login
For your users to be able to log in, you need to create a suitable endpoint. This is done by the auth controller in the auth module, which you create using the nest generate controller auth command. You can see the source code of the controller in this code:
import { Body, Controller, Post, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { User } from './user.entity';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() user: User) {
const validUser = await this.authService.validateUser(
user.username,
user.password,
);
if (validUser) {
return this.authService.login(user);
} else {
throw new UnauthorizedException();
}
}
}
The job of the auth controller is to provide your users with a POST endpoint with the path /auth/login to which they can send their login details to receive a JWT. To validate the user name and password, the controller uses the validateUser method of the auth service. If a valid user is found, a token is generated; otherwise, the controller returns an UnauthorizedException, which results in a return message to the client with a 401 status code.
For the controller and especially the JwtService used in the AuthService to work, you still need to slightly adjust the auth module. The code below contains the relevant details.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
@Module({
imports: [
TypeOrmModule.forFeature([User]),
PassportModule,
JwtModule.register({
secret: 'secret',
signOptions: { expiresIn: '1h' },
}),
],
providers: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
You can integrate the Passport module without any further configuration. Instead, you pass a configuration object to the register method of the Jwt module, similar to the configuration of TypeORM. Here, you define the basic configuration for your JWT. The secret field is especially critical because this information is used to sign your tokens— so it’s again a good candidate for removing it from the source code and including it in an external configuration such as environment variables. You must also specify how long the token should be valid. The shorter you choose this period, the more secure your tokens are, but the faster your users will have to renew them. At this point, you can already issue tokens, but your application’s resources are still unsecured.
Protecting Routes
To protect routes, Nest provides the concept of guards. You can think of a guard as a bouncer who only lets authorized requests through to the endpoint. In a first step, you must create such a guard for the JWT strategy. To do this, you use the nest generate class jwt.guard auth command to generate a new class in the auth directory. You can see the contents of this file here.
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
For your application to handle JWT correctly, you need to implement an appropriate strategy. This is a standard task, so Nest already takes a lot off your plate here. First, you must create a new file named jwt.strategy.ts in the src/auth directory. In this file, you now implement the JwtStrategy class. You can see the source code of this class here:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'secret',
});
}
async validate(payload: { sub: number; username: string }) {
return { id: payload.sub, username: payload.username };
}
}
At its core, the JwtStrategy simply derives from the PassportStrategy class and adapts it for your application.
Now you can link the JwtGuard to the movie controller. You can do this using the UseGuards decorator, which you can bind either to specific methods or to the complete controller. The listing below contains the modification of the controller.
import {
Body,
Controller,
Delete,
Get,
HttpCode,
Param,
Post,
Put,
UseGuards,
} from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/jwt.guard';
import { Movie } from './movie.entity';
import { InputMovie } from './movie.interface';
import { MovieService } from './movie.service';
@ApiTags('movies')
@UseGuards(JwtAuthGuard)
@Controller('movie')
export class MovieController {
...
}
If you now access one of the routes of the movie controller without having provided for a valid token before, you’ll receive a response with status code 401, unauthorized. For a valid response, you must first send your user name and the corresponding password to http://localhost:3000/auth/login in a POST request and use the token you receive there in the authorization header to send the request to the movie controller. This figure shows an example of such a request with Postman.
Editor’s note: This post has been adapted from a section of the Node.js: The Comprehensive Guide by Sebastian Springer. Sebastian is a JavaScript engineer at MaibornWolff. In addition to developing and designing both client-side and server-side JavaScript applications, he focuses on imparting knowledge. As a lecturer for JavaScript, a speaker at numerous conferences, and an author, he inspires enthusiasm for professional development with JavaScript. Sebastian was previously a team leader at Mayflower GmbH, one of the premier web development agencies in Germany. He was responsible for project and team management, architecture, and customer care for companies such as Nintendo Europe, Siemens, and others.
This post was originally published 3/2025.
Comments