Demystifying some of Nest.js with Passport

Romain Kelifa
7 min readMar 21, 2020

--

Photo by Serafima Lazarenko on Unsplash

Hi everybody !

Today I’m gonna go into using nest.js with passport to show some tips.

Many questions arose lately when trying to implement reusable pieces for authentication : here are some of the things I’ve learned in the process.

Why Nest.js ?

Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).

Basically, Nest.js comes packaged with good practices and dependency injections made possible with TypeScript decorators. While coding, it enables to avoid writing a lot of boilerplate code while allowing for some interesting combinations (e.g. writing both data definition, validation schema and ORM implementation inside a simple class).

Let’s code ! 💻

In this basic sample we’re gonna implement authentication with automatic redirection based on passport local strategy using session and cookies.

Requirements

  • Node : any version above 8 should be fine
  • Nest.js : 7.0.0
  • @nestjs/passport : 7.0.0
  • passport : 0.4.1
  • passport-local : 1.0.0
  • cookie-parser: 1.4.5
  • cookie-session: 1.4.0

First steps

In order to create this sample project, I just followed instructions from the documentation, using monorepo mode but it would just work fine in standard mode too.

Providers

Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider — services, repositories, factories, helpers, and so on. The main idea of a provider is that it can inject dependencies

Authentication Service

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-auth-service-ts

As you can see, it’s only a static dumb implementation but in real life you would probably tie it to some database or third-party API and of course add some more security checks.

Here we check our users to see if the username and password match for authentication. The method validateUser(username: string, password: string) is a passport requirement, which must return either a user (successful) or null (failure). Quite neat.

Passport Local Strategy

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-local-strategy-ts

The passport local strategy is the interface which will delegate authentication to the previously created service.

Notice the 2 parameters in the constructor ? These are gonna be automatically injected by Nest on startup. It allows for consuming authentication service previously created and extending the classic AuthModuleOptions to had for some classic redirection urls (successRedirect and failureRedirect).

Passport Session Serializer

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-session-serializer-ts

passport uses utility class to serialize and deserialize user infos, as stated in configure section of their website. It might look surprising, but because it inherits from PassportSerializer, it will automatically be injected by @nestjs/passport !

Guards

A guard is a class annotated with the @Injectable() decorator. Guards should implement the CanActivate interface.

Here we want to implement 2 different guards : one specifically for login, and another one to use on all the routes that should prevent access to unauthenticated users.

Login guard

The answer is straightforward: by using another, slightly different type of Guard. The @nestjs/passport module provides us with a built-in Guard that does this for us. This Guard invokes the Passport strategy and kicks off the steps described above (retrieving credentials, running the verify function, creating the user property, etc).

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-login-guard-ts

This time it inherits from AuthGuard to use @nestjs/passport and reduce boilerplate : calling super.canActivate(context) will trigger authentication by calling passport authenticate and our local strategy under the hood, while super.logIn(request) will create a login session.

Contrary to a classic vanilla passport implementation, there’s no exposed options to directly specify successRedirect and failureRedirect and allow for automatic redirection. (people at @nestjs/passport please correct me if I’m wrong)

Authenticated guard

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-authenticated-guard-ts

Here, thanks to passport library, there’s not much to do except checking for request.isAuthenticated() and that request.user contains the properties we expect ! (both previously set by passport in LoginGuard)

Exception Filters

Nest comes with a built-in exceptions layer which is responsible for processing all unhandled exceptions across an application. When an exception is not handled by your application code, it is caught by this layer, which then automatically sends an appropriate user-friendly response.

Here being able to catch specific exceptions allows for easy redirection.

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-auth-filter-ts

Here the filter will be used to catch both ForbiddenException and UnauthorizedException to automatically redirect to the login page from failureRedirect. Also notice that the LocalStrategy is injected to gracefully retrieve the redirection url.

Controllers

Controllers are responsible for handling incoming requests and returning responses to the client.

Routing can be here separated into 2 Controllers :

  • AuthController is bundled withAuthModule and only expose the /login endpoint.
  • AppController reuses AuthModule features to protect its /secured-page and redirect to /login-page all unauthenticated users.

Authentication Controller

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-auth-controller-ts

/login : API endpoint used for authentication.

  • @Post(‘login’) create a POST route at /login
  • @UseGuards(LoginGuard) protects this route and delegate authentication to the LoginGuard
  • @UseFilters(Unauthorized) catch specific exceptions to redirect to the failureRedirect url

Application Controller

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-app-controller-ts

/secured-page : e.g. HTML page which would require authenticated user.

  • @Get(‘secured-page’) create a GET route at /secured-page
  • @UseGuards(AuthenticatedGuard) protects this route with the AuthenticatedGuard
  • @UseFilters(Unauthorized) catch specific exceptions to redirect to the failureRedirect url

/login-page : e.g. HTML page with would provide username / password form and submit to /login for authentication.

Modules

A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure.

Here the purpose is to have a reusable AuthModule for authentication, and a main AppModule for application which consumes it.

Authentication Module

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-auth-module-ts

Here the module comes in 2 different implementations :

  • a default static implementation at @Module({ … })
upper part (decorator) for default static implementation
  • a dynamic implementation with static register({ successRedirect, failureRedirect, }: AuthModuleOptions): DynamicModule : here it allows actually for defining successRedirect and failureRedirect.
register method is for dynamic implementation, for example to provide different parameters

Please note something important here : at leastLocalStrategy in exports section is mandatory for both implementations since Unauthorized is meant to be consumed (injected) outside of AuthModule while itself consuming (injecting)LocalStrategy. Omitting to do so typically results in :

inner dependency injection missing in exports

Also one thing that befuddled me at the beginning : while being @Injectable(), Guards and Exception Filters doesn’t need to be specified inside exports section of the module. Just export them in a barrel file.

On the other hand if they themselves consume dependencies, which is the case here, these must be specified in the exports section.

Application Module

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-app-module-ts

Here, by adding AuthModule to AppModule imports it will automatically setup route from AuthController as per the module’s definition. As you guess,AppController ones will be automatically setup by adding it to AppModule controllers.

Integration tests

Last but not least, we want to test all this in real conditions 😎

https://gist.github.com/Roms1383/1aa8a5267de0824b497d0b58899f55d6#file-app-module-test-ts

It covers basic scenario, and please notice a couple of things here :

  • add some jest.setTimeout(...) if ever jest gets stuck while server app is being setup or currently running (not mandatory though).
  • initialize all the required server middlewares inside bootstrap and provide a teardown to stop server gracefully.
  • on testing routes with redirection like /login endpoint, set axios maxRedirects to 0 : request would be handled gracefully inside clients browser, but there’s some issue in Node environment still : session cookie infos wouldn’t be provided on redirection, typically resulting in :
missing cookie on axios redirection

Everything else is pretty much self-explanatory 🤓

A sample repository is available here :

Update 2020–03–22 : I actually realized today some parts could be improved : if you’re curious you can have a look at improvements branch 😄

Hope you enjoy it !
Do not hesitate to comment 💬 or show some love 👏 😃
Big up Felwin for both Passport feedback & fix for axios redirection ! 🌟

Update 2020–11–04 : if you’re interested in learning more, please have a look at my latest article below 👀

--

--

Responses (1)