Custom Authentication Strategy in Fastify using decorators, lifecycle hooks and fastify-auth
Tags: fastify, authentication, typscript
Published: 2020-01-11 23:35:49

To authenticate every incoming request before processing, Fastify lifecycle events and decorators can be used. If you want to have multiple authentication logics `fastify-auth` can be used.

For example, I have incoming requests which bring http cookie with a jwt token in it for authentication. Before processing each requests, I need to verify whether the jwt token is valid. 

For that,  I  am going to use a decorator  called `verifyJWT`, which is a custom function which has my logic in it. 

My `verifyJWT` will 

1. check for jwt expiry
2. then decode the jwt using my secret key and get the userId saved in it
3. if verification passes, continues execution and passes the userId to the handler function.
4. if verification failed, return unauthorized status to the client

Motivation is I don't want to write this verification logic in every route handler and prefer this happens as a hook before every request reaches the handler.

For that I am using `preValidation` lifecycle hook from fastify.

I will abstract away the verification logic as a decorator function in my fastify instance. So that, I can simply call

```
fastify.verifyJWT() 

```

Needed package:
fastify-auth - a fastify plugin which supports multiple authentication strategies at the same time


##Step 1: Configuration

We can register the fastify-auth plugin and the decorators we wrote to the fastify instance.
```
//index.ts

import decorators from "./decorators";
import * as fastifyAuth from "fastify-auth";


const fastifyApp = fastify(fastifyConfig)
                   .register(decorators)
                   .register(fastifyAuth);

```

##Step 2: Write some decorators

Writing decorators will help us attach custom functions to the instance and we can call them elegantly like

```

fastify.verifyJWT() //verifyJWT is the name of the decorator function

```


```
//decorators.ts

import * as fastifyPlugin from "fastify-plugin";

export default fastifyPlugin( (fastify,options, next) => {
  fastify.decorate("verifyJWT", function(req: any, rpl: any, done: any) {
    const cookie = req.cookies[COOKIE_NAME];

    const verificationCallback = ({ userId, err }: TokenDecoded) => {
      if (userId) {
        //pass this to the handler function so that it can use it to 
        //identify the user and process his data
        req.params.userId = userId;
        return done();
      }
      done(err);
    };
    //verifyToken gives userId in case of successful decoding
    //gives err msg in case of error 
    verifyToken(cookie, verificationCallback);
  });
  next();
});

```

Note that we call the callback called `done` here when our authentication process is complete. If called simply, the execution will be passed to the handler of the route. If passed with some error message, this will automatically throw unauthorized status with the error message passed in.

Also, I need the userId retrieved in the handler function. For that, I am using the following and can be accessed using `request.params` in the handler.

```
        req.params.userId = userId;
```


##Step 3: Add the authentication strategy to the route

We are using the `preValidation` lifecycle method to validate each request  using the `verifyJWT` method. 

```
// routes/wishlist.ts

import * as fastifyPlugin from "fastify-plugin";

export const fastifyPlugin((fastify, options, next) => {
  fastify.route({
    method: "GET",
    url: "/wishlist",
    preValidation: fastify.auth([fastify.verifyJWT]),
    handler: (req, rpl) => {
      //remember we set userId into req params in previous step?
      const { userId } = req.params;
      //get user from the db using this id
    }
  });
  next();
})

```

Note: Typescript will complain that it doesn't know a method verifyJWT  in the fastify instance. To fix that, we need to extend the typings for Fastify. 

Lets check the value of `typeRoots` in `tsconfig.json`. 
Mine has`"typeRoots": ["node_modules/@types", "types"]`.

So in `types` folder in the root of the project,

```
// in types/fastify/index.d.ts

import fastify from "fastify";
import { ServerResponse, IncomingMessage, Server } from "http";

declare module "fastify" {
  export interface FastifyInstance<
    HttpServer = Server,
    HttpRequest = IncomingMessage,
    HttpResponse = ServerResponse
  > {
    verifyJWT(): void;
    someOtherDecorator(rpl: any, userId: string) => void
  }
}

```


The `fastify.auth` function takes an array of authentication functions. We could pass any number of functions inside that  and those functions will be checked on a `OR` condition. Say,
I pass in 

```
    preValidation:fastify.auth(
         [
          fastify.verifyJWT,
          fastify.verifyUsernameAndPassword
         ]
    )

```

Now, if any one of these auth function passes, it is considered as a authenticated request. You could add a AND condition also using 

```
preValidation: fastify.auth([
        fastify.verifyIsAdmin,
        fastify.verifyIsSuperAdmin
      ], {
        relation: 'and'
      }),
```