2FA with NestJs & passeport using Google Authenticator
Tags: nestjs, authentication, beginners, typescript
Published: 2022-07-07 12:45:30

I recently had to implement a two factor authentication on a project for my company and it was a whole new thing for me. Sure I had already used 2fa before but I had never implemented it.

What is 2fa ? Well we all know that passwords aren't really secure enough to avoid security breaches so... 

> 2FA is an extra layer of security used to make sure that people trying to gain access to an online account are who they say they are. First, a user will enter their username and a password. Then, instead of immediately gaining access, they will be required to provide another piece of information.

In this case, I was asked to use the google authenticator app to generate a 2fa code that would be used to authenticate the user after the login step.

I'll be using nestJs with passportjs. Here's the GitHub link if you want to check it out: https://github.com/MatthieuHahn/2fa

# Initializing the NestJS project with basic login password authentication

Let's create a new nestJs project.

`nest new 2fa`

## Creating the authentication module

First let's install the nestJs passeport dependencies and types. We'll need them in the next step.

`yarn add @nestjs/passport passport passport-local`
`yarn add -D @types/passport-local`

Then, we'll generate the authentication module, controller and service.

`nest generate resource authentication`

It'll ask you a few questions: choose REST API and then answer No (it's an auth module, we don't need CRUD endpoints)

Then we'll create a user module, service and interface

`nest generate module users`

`nest generate service users`

```
export interface User {
  userId: number;
  username: string;
  password: string;
}
```

The users service will contain fake data, but, of course, you should add a data layer to persist data 😅

```
import { Injectable } from '@nestjs/common';
import { User } from "./user.entity";

@Injectable()
export class UsersService {
  private readonly users = [
    {
      userId: 1,
      username: 'john',
      password: 'changeme',
    },
    {
      userId: 2,
      username: 'maria',
      password: 'guess',
    },
  ];

  async findOne(username: string): Promise<User | undefined> {
    return this.users.find(user => user.username === username);
  }
}
```

Let's add a validateUser method that will check if the user and password that we'll send are a match with our data. 


```
import { Injectable } from '@nestjs/common';
import { UsersService } from "../users/users.service";
import { User } from "../users/user.entity";

@Injectable()
export class AuthenticationService {
  constructor(private usersService: UsersService) {
  }
  
  async validateUser(email: string, pass: string): Promise<Partial<User>> {
    const user = await this.usersService.findOne(email);
    try {
      // Of course, we should consider encrypting the password
      const isMatch = pass === user.password;
      if (user && isMatch) {
        const { password: _, ...userWithoutPassword } = user;
        
        return userWithoutPassword;
      }
    } catch (e) {
      return null;
    }
  }
}

```

If you are not familiar with nestJs, this framework provides a UseGuard decorator that will act as an auth middleware which can rely on the passportjs library. So let's use this feature and the nestJs passport library to manage the user/password login.

First we define the local auth guard which extends the passportjs local strategy.

local-auth.guard.ts

```
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

```

And then we define the local authentication strategy. The constructor contains the auth fields that will be sent by the front-end via the login POST route (email, password).
The validate method will use the validateUser method we created earlier. If it returns no user, then it will throw a 401 Unauthorized error, else it will return the user without the password.

local.strategy.ts

```
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthenticationService } from '../authentication.service';
import { User } from '../../users/user.entity';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authenticationService: AuthenticationService) {
    super({
      usernameField: 'email',
      passwordField: 'password',
    });
  }

  async validate(email: string, password: string): Promise<Partial<User>> {
    const userWithoutPsw = await this.authenticationService.validateUser(email, password);
    if (!userWithoutPsw) {
      throw new UnauthorizedException();
    }
    return userWithoutPsw;
  }
}
```

We can now create the login method in the authentication controller and service. For now it'll only return the user's email. But later on, we'll add a Jwt access token

First we add the login method to the authentication service.

```
async login(userWithoutPsw: Partial<User>, isTwoFactorAuthenticated = false) {
    const payload = {
      email: userWithoutPsw.email,
      isTwoFactorAuthenticationEnabled: !!userWithoutPsw.isTwoFactorAuthenticationEnabled,
      isTwoFactorAuthenticated,
    };

    return {
      email: payload.email,
    };
  }
```

Then we create the controller route with the LocalAuthGuard

```
@UseGuards(LocalAuthGuard)
@Post('login')
@HttpCode(200)
async login(@Request() req) {
  const userWithoutPsw: Partial<User> = req.user;

  return this.authenticationService.login(userWithoutPsw);
}
```

Ok, so now, if we send a post request on the /authentication/login route with correct credentials, it will return the user's email. Let's add the Jwt management now.

First we add the nestJs Jwt package.

`yarn add @nestjs/jwt`

Then we have to register the JwtModule in the Authentication module in order to be able to use it. The secret should, of course, be secret and in an .env file.

```
JwtModule.register({
    secret: 'secret',
    signOptions: { expiresIn: '1d' },
  })
```

Then we just have to add the JwtService to the AuthenticationService and use it to generate the access_token we'll return to the front-end.
Here's what the authentication service should look like now.

```
import { Injectable } from '@nestjs/common';
import { UsersService } from "../users/users.service";
import { User } from "../users/user.entity";
import { JwtService } from "@nestjs/jwt";

@Injectable()
export class AuthenticationService {
  constructor(private usersService: UsersService, private jwtService: JwtService) {
  }

  async validateUser(email: string, pass: string): Promise<Partial<User>> {
    const user = await this.usersService.findOne(email);
    try {
      // Of course, we should consider encrypting the password
      const isMatch = pass === user.password;
      if (user && isMatch) {
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const { password: _, ...userWithoutPassword } = user;
        return userWithoutPassword;
      }
    } catch (e) {
      return null;
    }
  }

  async login(userWithoutPsw: Partial<User>) {
    const payload = {
      email: userWithoutPsw.email,
    };

    return {
      email: payload.email,
      access_token: this.jwtService.sign(payload),
    };
  }
}
```

Now the frontend has a Jwt token it'll be able to use to authenticate requests to the backend. But how will the backend manage the Authorization token ?
Well, let's create a JwtAuthGuard using passportjs jwt package.

`yarn add passport-jwt`

We can now create the jwt strategy which is based on passportjs.

jwt.strategy.ts

```
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { UsersService } from '../../users/users.service';
import { TokenPayload } from '../token-payload.entity';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly userService: UsersService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'secret',
    });
  }

  async validate(payload: TokenPayload) {
    const user = await this.userService.findOne(payload.email);

    if (user) {
      return user;
    }
  }
}

```

And create the JwtAuthGuard.

jwt-auth.guard.ts

```
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

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

So for now, we have a complete basic authentication using JWT to authenticate requests. But as we said before, it is based on a user/password login and therefore it is not very secure. So let's add the two factor authentication.

## Two factor authentication

Here's the login flow for 2fa authentication:

1. The user logs in with his email and password
2. If the 2fa is not enabled, he can enable it using the turn-on route. This will generate a QrCode that the user will scan with the google authenticator app. 
3. The use then uses the random code the app has generated to authenticate

### Creating the 2fa system

First we have to create a unique secret for every user that turns on 2fa, but we'll also need a special otp authentication url that we'll be using later to create a QrCode. The otplib package is a good match, so let's install it.

`yarn add otplib`

We should also update the user interface and add the twoFactorAuthenticationSecret property.

```
export interface User {
  userId: number;
  email: string;
  username: string;
  password: string;
  twoFactorAuthenticationSecret: string;
}
```

Then we create a method to generate the secret and otpAuthUrl in the authentication service and return both of them. The AUTH_APP_NAME is the name that will appear in the google authenticator app.


```
async generateTwoFactorAuthenticationSecret(user: User) {
    const secret = authenticator.generateSecret();

    const otpauthUrl = authenticator.keyuri(user.email, 'AUTH_APP_NAME', secret);

    await this.usersService.setTwoFactorAuthenticationSecret(secret, user.userId);

    return {
      secret,
      otpauthUrl
    }
  }
```

We have to update the user with the secret that has just been generated. Once again, this should all be in a database.

```
  async setTwoFactorAuthenticationSecret(secret: string, userId: number) {
    this.users.find(user => user.userId === userId).twoFactorAuthenticationSecret = secret;
  }
```

Now, we can generate the QrCode that will be used to add our application to the google authenticator app.

`yarn add qrcode`

Let's add the generate method in the authentication service.

```
  import { toDataURL } from 'qrcode';

  async generateQrCodeDataURL(otpAuthUrl: string) {
    return toDataURL(otpAuthUrl);
  }
```

Now we need to offer the possiblity for the user to turn on the 2fa. So let's add another property to the user interface.

```
export interface User {
  userId: number;
  email: string;
  username: string;
  password: string;
  twoFactorAuthenticationSecret: string;
  isTwoFactorAuthenticationEnabled: boolean;
}
```

Add the turnOn method in the users service

```
  async turnOnTwoFactorAuthentication(userId: number) {
    this.users.find(user => user.userId === userId).isTwoFactorAuthenticationEnabled = true;
  }
```

Add the method that will verify the authentication code with the user's secret

```
isTwoFactorAuthenticationCodeValid(twoFactorAuthenticationCode: string, user: User) {
    return authenticator.verify({
      token: twoFactorAuthenticationCode,
      secret: user.twoFactorAuthenticationSecret,
    });
  }
```

Add the turn on route in the authentication controller

```
 @Post('2fa/turn-on')
  @UseGuards(JwtAuthGuard)
  async turnOnTwoFactorAuthentication(@Req() request, @Body() body) {
    const isCodeValid =
      this.authenticationService.isTwoFactorAuthenticationCodeValid(
        body.twoFactorAuthenticationCode,
        request.user,
      );
    if (!isCodeValid) {
      throw new UnauthorizedException('Wrong authentication code');
    }
    await this.usersService.turnOnTwoFactorAuthentication(request.user.id);
  }
```

### Logging in with 2fa

Let's add a login with 2fa method in the authentication service. The difference with the default login function is that we add the 2fa status in the payload. 

```
  async loginWith2fa(userWithoutPsw: Partial<User>) {
    const payload = {
      email: userWithoutPsw.email,
      isTwoFactorAuthenticationEnabled: !!userWithoutPsw.isTwoFactorAuthenticationEnabled,
      isTwoFactorAuthenticated: true,
    };

    return {
      email: payload.email,
      access_token: this.jwtService.sign(payload),
    };
  }
```

We can now create the 2fa authentication route in the controller. If the code sent in the post body is valid, then we try to login with 2fa else we throw an error.


```
@Post('2fa/authenticate')
  @HttpCode(200)
  @UseGuards(JwtAuthGuard)
  async authenticate(@Request() request, @Body() body) {
    const isCodeValid = this.authenticationService.isTwoFactorAuthenticationCodeValid(
      body.twoFactorAuthenticationCode,
      request.user,
    );

    if (!isCodeValid) {
      throw new UnauthorizedException('Wrong authentication code');
    }

    return this.authenticationService.loginWith2fa(request.user);
  }
```

It is now possible to create an AuthGuard strategy based on Jwt and the 2fa status. If the 2fa is not turned on then we can rely on the jwt only, if the 2fa is enabled then we check if the user is 2fa authenticated.

jwt-2fa.strategy.ts

```
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { UsersService } from '../../users/users.service';

@Injectable()
export class Jwt2faStrategy extends PassportStrategy(Strategy, 'jwt-2fa') {
  constructor(private readonly userService: UsersService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'secret',
    });
  }

  async validate(payload: any) {
    const user = await this.userService.findOne(payload.email);

    if (!user.isTwoFactorAuthenticationEnabled) {
      return user;
    }
    if (payload.isTwoFactorAuthenticated) {
      return user;
    }
  }
}

```

jwt-2fa-auth.guard.ts

```
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class Jwt2faAuthGuard extends AuthGuard('jwt-2fa') {}
```

Let's see how this goes right now.

## Testing

So first we'll do a POST request to log in with the user and password:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rkygvo4iai4s9euvk9rr.png)

This will return the following:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0qo3gnfsa68iialgqd2.png)

Then, we need to get the QrCode to add our app to the google authenticator app

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wwja2hfavmndtgfnt4i.png)

This will return a base64 data url which in turn will happen to be a QrCode

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1gpy9ome35jjeyn8reh.png)

If you scan this with the Google Authenticator App it should add your app:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ung8ipxi26gmjpuv3kwd.jpeg)

And then you should be able to call the authenticate route with the current code from the google authenticator app