2FA in NestJS Application

Hello everyone ! I am going to describe Two Factor Authentication(2FA) in NestJS using a Todo application. So let’s jump in.

N.B. I already wrote this tutorial series for Todo application using — NestJS, JWT, PostgresSQL, PgAdmin4 and Docker. Feel free to look at it. Link- https://tushar-chy.medium.com/a-simple-todo-application-with-nestjs-typeorm-postgresql-swagger-pgadmin4-jwt-and-docker-caa2742a4295
and the code — GitHub

N.B. Refresh token generator using the same application. Link- https://tushar-chy.medium.com/jwt-refresh-token-generator-in-nestjs-application-54c5ab2c0da3
I recommand to understand the Refresh-token-generator. It will be better for this tutorial and code — GitHub

We are going to use otplib for secret generator & qrcode for keyUri. So let’s run the below command

// under docker env
docker-compose run nestjs npm install otplib grcode
// without docker env
npm install otplib grcode

First, let’s update the .env file and a line


And also add a line in config/development.yml (Those who are not going to use docker as their environment)

twoFactorAppName: 'todo-app'

Now we need to store 2FA secret code & the visibility of 2FA in DB. So let’s update the auth/entity/user.entity.ts file

@Column({ nullable: true })
twoFactorAuthSecret?: string
@Column({ default: false })
public isTwoFactorEnable: boolean

Let’s do the migration using this below command

// under docker environment
docker-compose run nestjs npm run typeorm:generate anyNameYouLike
docker-compose run nestjs npm run typeorm:run
// without docker environment
npm run typeorm:generate anyNameYouLike
npm run typeorm:run

N.B. You have to empty the migration folder before you run the above command and if you have stale migration files.

update the auth/interface/jwt-payload.interface.ts file

export interface JwtPayload {
isTwoFactorEnable?: boolean
isTwoFaAuthenticated?: boolean

Update the validateUserPassword() in auth/repository/user.repository.ts and the signIn() in auth/service/auth.service.ts files and create auth/dto/two-fa-auth.dto.ts file

Create src/auth/strategy/jwt-2fa-strategy.ts, auth/service/two-factor-auth.service.ts, auth/controller/two-factor-auth.controller.ts & guards/jwt-two-factor.guard.ts fileguards/jwt-two-factor.guard.ts

As you noticed that, We didn’t update @UseGuards(JwtAuthenticationGuard) To @UseGuards(JwtTwoFactorGuard). Because if we use JwtTwoFactorGuard then we will not able to generate QR-code. The concept is-
1. After sign-in we will get the accessToken
2. With that token we will call /generate-qr API to generate QR which will be scanned by the google-authenticator application and generate codes.
3. After that we will call /turn-on-qr to turn on the QR as well as check the inserted code from the google-authenticator app in /authenticate API.
4. If those steps are correct then it will sign-in again & set the JwtTwoFactorGuard all over the application.
5. After successful login we don’t need to generate the QR code frequently. Just get the code & submit it through /authenticate API.

N.B. You will find those logics at the below codes

Update the validate() in auth/strategy/jwt-refresh-strategy.ts file

async validate(payload: JwtPayload) {
const { username } = payload;
const user = await this.userRepository.findOne({ username });
if (!user) {
throw new UnauthorizedException();
if (!user.isTwoFactorEnable) {
return user;
if (payload.isTwoFaAuthenticated) {
return user;

Update todo/todo.controller.ts & user/user.controller.ts file

@UseGuards(JwtAuthenticationGuard) To @UseGuards(JwtTwoFactorGuard)

Update auth/controller/auth.controller.ts file

@Body(ValidationPipe) signinCredentialsDto: SignInCredentialsDto
): Promise<{ accessToken: string, refreshToken?: string, user?: JwtPayload }>{
return this.authService.signIn(signinCredentialsDto);
async refreshToken(
@GetUser() user: User,
@Body() token: RefreshTokenDto
const user_info = await this.authService.getUserIfRefreshTokenMatches(token.refresh_token, user.username)
if (user_info) {
const userInfo = {
username: user_info.username,
user_info: user_info.user_info
if (user.isTwoFactorEnable) {
userInfo['isTwoFaAuthenticated'] = true;
userInfo['isTwoFactorEnable'] = user.isTwoFactorEnable;

return this.authService.getNewAccessAndRefreshToken(userInfo)
} else{
return null

And update auth/auth.module.ts file

controllers: [AuthController, TwoFactorAuth],
providers: [
exports: [

So this is it. Just play around with swagger/postman And obviously the code in GitHub. I think this tutorial might help you.

Thank You !!!



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tushar Roy Chowdhury

Tushar Roy Chowdhury


I am a passionate programmer and always keep eye on new technologies and scrutinize them until getting into shape.