Definitive guide for Nest.js guards and Passport

Photo by Zhen Hu on Unsplash

Hi everybody !

Today, I’m gonna summarize key aspects of implementing guards with Nest.js and Passport. They will be discussed in Express context because… huh well, I haven’t done any Fastify in a couple of years 😅

Please note that if you want to jump straight up to the code and related integration tests, you can have a look at the related sample repo directly :

Is this useful to me ?

If you’re wondering how to :

  • use injection in guard / strategy
  • implement multiple strategies
  • implement multiple guards

Yeah, then this is for you 😃

Let’s dive in ! 💻

First, let’s have a quick refresher on both Guard and AuthGuard.

Guard

Lifecycle

In Nest.js, here is the order in which decorators get called :

  1. Middleware
  2. Guard
  3. ValidationPipe
  4. Controller route handler method

Hence, for example, don’t expect a 400 / Validation failed error with a malformed payload if ever your request does not pass the Guard in the first place.

AuthGuard

Lifecycle

To put it simply, it does :

  1. extends Guard
  2. pipe the options to the underlying passport strategy
  3. call the authorization logic of the underlying passport strategy
  4. finally call PassportStrategy's verify method (which, under the hood, calls validate on Nest.js PassportStrategy), allowing you to plug your own additional authorization logic

Multiple strategies

The golden rule to remember : multiple strategies will succeed as long as at least one passes.

Use cases

Typical use case include providing alternate ways to authorize requests.

Example

Let’s implement both a JWT strategy for the Bearer request header and a JWT strategy for the request query (which will retrieve the JWT from a token query parameter) :

jwt-bearer.strategy.ts / jwt-query.strategy.ts (made with ❤️ with Polacode)

As you can notice, the only thing that differ between these 2 classes is the jwtFromRequest option that is used with passport-jwt strategy and the name given to the strategies (here jwt.bearer and jwt.query), so you could of course refactor it in a nicer way.

Then, to use them all you have to do is create the corresponding AuthGuard :

jwt.guard.ts (made with ❤️ with Polacode)

And use it in your controller :

some controller (made with ❤️ with Polacode)

This way, as long as a valid JWT is sent either in the expected header or query parameter, you user will be allowed by the JwtGuard.

Multiple guards

The golden rule to remember : multiple guards will succeed as long as all of them passes.

Use cases

Typical use cases include reusing different combinations of Guard(s) on different Controller routes.

Example

Let’s say we have 2 routes, both expect a valid JWT to be provided but one requires the user to be an admin while the other one implement some kind of IP rate limiting.

Of course for rate limiting you would typically use some nice library like nestjs-rate-limiter but for the purpose of this example I will use a dummy homemade implementation (please don’t use this in production).

Let’s first assume we have this controller :

some controller (made with ❤️ with Polacode)

I’m gonna reuse the JwtGuard from the former example, and add on top of it :

role.guard.ts / rate.guard.ts (made with ❤️ with Polacode)

The RoleGuard is nothing more than the one depicted in Nest.js documentation : it checks that the authenticated user has the required role.

The RateGuard will save request IP and compare it with previous ones : provided the limit has been reached, it will prevent the request from completing successfully.

Here again, you can find the details of the associated services and decorators implementation in the sample repo.

Context & injection in guard / strategy

I’ve often seen and wondered myself what I could actually use or inject in AuthGuard / PassportStrategy.

Context

  • Guard has access to both ExecutionContext and Reflector, so it can retrieve metadata from Controller’s route handler
  • Strategy can only access Reflector but notExecutionContext

Injection

  • Strategy can be injected with any service (or provider, as Nest.js also call them)
  • Guard is not meant to be injected with any service

A workaround to inject service(s) in a guard it is to use Jinnisolution but it implies to make both your guard and service(s) global, so I would strongly advice against it.

What if I need both ?

There’s a way :

  1. retrieve the metadata in the Guard
  2. store them on the request before triggering strategy fromsuper.canActivate
  3. then retrieve them from the request in Strategy’s validate and perform your logic with injected services

Seems like a weird solution ?
That’s actually what passport already does with req.user 👈

Example

That’s actually what I’ve done for the RateGuard :

some controller / rate.guard.ts / rate.strategy.ts (made with ❤️ with Polacode)
  1. the RATE_LIMIT is defined on the Controller route
    this way a different rate limit could be set for different routes
  2. it is then retrieved in the RateGuard and set on the request
    request.limit = limit
  3. finally it’s retrieved from the request in the RateStrategy and compared to check whether to allow the request or not
Warner Bros ©

I hope it will be useful to you and save you some time digging into the documentation / stackoverflow / source code 😃

Stay safe and take care of your relatives !

Senior developer