63-Nodejs Course 2023: Auth: Access Token Database Collection
Tags: node, typescript, mongodb, fastify
Published: 2022-11-12 18:42:37

---
title: 63-Nodejs Course 2023: Auth: Access Token Database Collection
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5vdkxzrocea5caeoali.png
# Use a ratio of 100:42 for best results.
---

Previously, we saw how to implement JWT in our application, now we need to store the access token in our database, so we need to create it.

## Why would we need to store the access token in the database?

We need to store the access token in the database to be able to revoke (Remove) it, so if the user logged in from a device and he/she wants to logout from that device only, we can revoke the access token of that device only.

Also, let's assume that the user wants to change his/her password, in that case we should revoke all the access tokens of the user, so he/she can't use the old password to login.

## Create Access Token Collection

Now, as the auth system grows up, let's create a new folder called `auth` inside the `src/core` folder, and inside it let's create a new directory called `models` then create a new file called `access-token.ts`.

```ts
// src/core/auth/models/access-token.ts
import { Model } from "core/database";

export default class AccessToken extends Model {
  /**
   * {@inheritDoc}
   */
  public static collectionName = "accessTokens";
}
```

A simple regular model that has only the collection name.

Now let's create a new file called `index.ts` inside the `src/core/auth` folder.

We'll export from it a function called `registerAuthRoutes` which will manage all the auth routes.

> We'll talk later about service providers concept.

Let's first update `request` class to change the property name of Fastify request to be `baseRequest` instead of `request`.

```ts
import events from "@mongez/events";
import { get, only } from "@mongez/reinforcements";
import { Route } from "core/router/types";
import { validateAll } from "core/validator";
import { FastifyReply, FastifyRequest } from "fastify";
import response, { Response } from "./response";
import { RequestEvent } from "./types";
import UploadedFile from "./UploadedFile";

export class Request {
  /**
   * Fastify Request object
   */
  public baseRequest!: FastifyRequest;

  /**
   * Response Object
   */
  protected response: Response = response;

  /**
   * Route Object
   */
  private route!: Route;

  /**
   * Parsed Request Payload
   */
  protected payload: any = {};

  /**
   * Set request handler
   */
  public setRequest(request: FastifyRequest) {
    this.baseRequest = request;

    this.parsePayload();

    return this;
  }

  /**
   * Parse the payload and merge it from the request body, params and query string
   */
  protected parsePayload() {
    this.payload.body = this.parseBody();
    this.payload.query = this.baseRequest.query;
    this.payload.params = this.baseRequest.params;
    this.payload.all = {
      ...this.payload.body,
      ...this.payload.query,
      ...this.payload.params,
    };
  }

  /**
   * Parse body payload
   */
  private parseBody() {
    const body: any = {};
    const requestBody = this.baseRequest.body as Record<string, any>;

    for (const key in requestBody) {
      const keyData = requestBody[key];

      if (Array.isArray(keyData)) {
        body[key] = keyData.map(this.parseInputValue.bind(this));
      } else {
        body[key] = this.parseInputValue(keyData);
      }
    }

    return body;
  }

  /**
   * Set Fastify response
   */
  public setResponse(response: FastifyReply) {
    this.response.setResponse(response);

    return this;
  }

  /**
   * Set route handler
   */
  public setRoute(route: Route) {
    this.route = route;

    // pass the route to the response object
    this.response.setRoute(route);

    return this;
  }

  /**
   * Trigger an http event
   */
  protected trigger(eventName: RequestEvent, ...args: any[]) {
    return events.trigger(`request.${eventName}`, ...args, this);
  }

  /**
   * Listen to the given event
   */
  public on(eventName: RequestEvent, callback: any) {
    return this.trigger(eventName, callback);
  }

  /**
   * Execute the request
   */
  public async execute() {
    // check for middleware first
    const middlewareOutput = await this.executeMiddleware();

    if (middlewareOutput !== undefined) {
      return middlewareOutput;
    }

    const handler = this.route.handler;

    // 👇🏻 check for validation using validateAll helper function
    const validationOutput = await validateAll(
      handler.validation,
      this,
      this.response,
    );

    if (validationOutput !== undefined) {
      return validationOutput;
    }

    // call executingAction event
    this.trigger("executingAction", this.route);
    const output = await handler(this, this.response);

    // call executedAction event
    this.trigger("executedAction", this.route);

    return output;
  }

  /**
   * Execute middleware list of current route
   */
  protected async executeMiddleware() {
    if (!this.route.middleware || this.route.middleware.length === 0) return;

    // trigger the executingMiddleware event
    this.trigger("executingMiddleware", this.route.middleware, this.route);

    for (const middleware of this.route.middleware) {
      const output = await middleware(this, this.response);

      if (output !== undefined) {
        this.trigger("executedMiddleware");
        return output;
      }
    }

    // trigger the executedMiddleware event
    this.trigger("executedMiddleware", this.route.middleware, this.route);
  }

  /**
   * Get request input value from query string, params or body
   */
  public input(key: string, defaultValue: any = null) {
    return get(this.payload.all, key, defaultValue);
  }

  /**
   * Get request body
   */
  public get body() {
    return this.payload.body;
  }

  /**
   * Parse the given data
   */
  private parseInputValue(data: any) {
    // data.value appears only in the multipart form data
    // if it json, then just return the data
    if (data.file) return data;

    if (data.value !== undefined) return data.value;

    return data;
  }

  /**
   * Get request file in UploadedFile instance
   */
  public file(key: string): UploadedFile | null {
    const file = this.input(key);

    return file ? new UploadedFile(file) : null;
  }

  /**
   * Get request params
   */
  public get params() {
    return this.payload.params;
  }

  /**
   * Get request query
   */
  public get query() {
    return this.payload.query;
  }

  /**
   * Get all inputs
   */
  public all() {
    return this.payload.all;
  }

  /**
   * Get only the given keys from the request data
   */
  public only(keys: string[]) {
    return only(this.all(), keys);
  }

  /**
   * Get boolean input value
   */
  public bool(key: string, defaultValue = false) {
    const value = this.input(key, defaultValue);

    if (value === "true") {
      return true;
    }

    if (value === "false") {
      return false;
    }

    return Boolean(value);
  }

  /**
   * Get integer input value
   */
  public int(key: string, defaultValue = 0) {
    const value = this.input(key, defaultValue);

    return parseInt(value);
  }

  /**
   * Get float input value
   */
  public float(key: string, defaultValue = 0) {
    const value = this.input(key, defaultValue);

    return parseFloat(value);
  }

  /**
   * Get number input value
   */
  public number(key: string, defaultValue = 0) {
    const value = Number(this.input(key, defaultValue));

    return isNaN(value) ? defaultValue : value;
  }
}

const request = new Request();

export default request;
```

I just replaced the property `request` to be `baseRequest`.

Now let's move our guest request to the auth index.

```ts
// src/core/auth/index.ts
export { default as registerAuthRoutes } from "./registerAuthRoutes";
```

Now let's create `registerAuthRoutes` function.

```ts
// src/core/auth/registerAuthRoutes.ts
import { Request } from "core/http/request";
import { Response } from "core/http/response";
import router from "core/router";

export default function registerAuthRoutes() {
  // now let's add a guests route in our routes to generate a guest token to our guests.
  router.post("/guests", (request: Request, response: Response) => {
    const token = request.baseRequest.jwt.sign({ userType: "guest" });

    return response.send({
      accessToken: token,
      userType: "guest",
    });
  });
}
```

We just moved the code from the `connectToServer` function and moved it here.

Now let's move the hook as well.

```ts
// src/core/auth/registerAuthRoutes.ts
import { Response } from "core/http/response";
import { getServer } from "core/http/server";
import router from "core/router";

export default function registerAuthRoutes() {
  // get server instance
  const server = getServer();

  // now let's add a guests route in our routes to generate a guest token to our guests.
  router.post("/guests", (_request, response: Response) => {
    const token = server.jwt.sign({ userType: "guest" });

    return response.send({
      accessToken: token,
      userType: "guest",
    });
  });

  // now let's add an event to validate the request token
  server.addHook("onRequest", async (request, reply) => {
    if (request.url === "/guests") return;

    try {
      await request.jwtVerify();
    } catch (err) {
      reply.status(401).send({
        error: "Unauthorized: Invalid Access Token",
      });
    }
  });
}
```

Now we moved the `onRequest` event to be in the `registerAuthRoutes` function so we keep our `connectToServer` function clean.

Now the final look of the `connectToServer` function.

```ts
import config from "@mongez/config";
import router from "core/router";
import registerHttpPlugins from "./plugins";
import response from "./response";
import { getServer } from "./server";

export default async function connectToServer() {
  const server = getServer();

  registerHttpPlugins();

  // call reset method on response object to response its state
  server.addHook("onResponse", response.reset.bind(response));

  router.scan(server);

  try {
    // 👇🏻 We can use the url of the server
    const address = await server.listen({
      port: config.get("app.port"),
      host: config.get("app.baseUrl"),
    });

    console.log(`Start browsing using ${address}`);
  } catch (err) {
    console.log(err);

    server.log.error(err);
    process.exit(1); // stop the process, exit with error
  }
}
```

## Registering auth routes

If you can recall, and i doubt you would, we have a centralized routes file to register all of our app routes inside it , which is located under `src/app/routes.ts`, there where we'll register our auth routes.

```ts
// src/app/routes.ts
// users module
import "app/users/routes";
import { registerAuthRoutes } from "core/auth";

// import auth routes
registerAuthRoutes();
```

## Saving Access Token In Database

Now we've created our token, we need to register it in our database, luckily we don't really want to wait until the database saves the token, so we can just call the `create` method directly without waiting for the promise to resolve.

```ts
// src/core/auth/registerAuthRoutes.ts
import { Response } from "core/http/response";
import { getServer } from "core/http/server";
import router from "core/router";
// 👇🏻 import the AccessToken model
import AccessToken from "./models/access-token";

export default function registerAuthRoutes() {
  // get server instance
  const server = getServer();

  // now let's add a guests route in our routes to generate a guest token to our guests.
  router.post("/guests", (_request, response: Response) => {
    const token = server.jwt.sign({ userType: "guest" });

// 👇🏻 create the access token document in the database
    AccessToken.create({
      token,
      userType: "guest",
    });

    return response.send({
      accessToken: token,
      userType: "guest",
    });
  });

  // now let's add an event to validate the request token
  server.addHook("onRequest", async (request, reply) => {
    if (request.url === "/guests") return;

    try {
      await request.jwtVerify();
    } catch (err) {
      reply.status(401).send({
        error: "Unauthorized: Invalid Access Token",
      });
    }
  });
}
```

And that's it!, now we can capture the access token and save it in the database.

## 🎨 Conclusion

We created a database collection for access tokens and learnt the reason behind that behavior, we also also cleaned up our `connectToServer` function and moved the auth routes to a separate file.

## 🚀 Project Repository

You can find the latest updates of this project on [Github](https://github.com/hassanzohdy/nodejs-2023)

## 😍 Join our community

Join our community on [Discord](https://discord.gg/pb2vmdfhGf) to get help and support (Node Js 2023 Channel).

## 🎞️ Video Course (Arabic Voice)

If you want to learn this course in video format, you can find it on [Youtube](https://www.youtube.com/playlist?list=PLGO8ntvxgiZMJc7RN2lIq9WmMOlWZGzmz), the course is in Arabic language.

## 📚 Bonus Content 📚

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

* [Event Driven Architecture: A Practical Guide in Javascript](https://dev.to/hassanzohdy/event-driven-architecture-the-best-paradigm-that-i-love-to-work-with-in-javascript-and-node-js-1gnk)
* [Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript](https://dev.to/hassanzohdy/best-practices-for-case-styles-camel-pascal-snake-and-kebab-case-in-node-and-javascript-55oi)
* [After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
](https://dev.to/hassanzohdy/after-6-years-of-practicing-mongodb-here-are-my-thoughts-on-mongodb-vs-mysql-574b)

Packages & Libraries

* [Collections: Your ultimate Javascript Arrays Manager](https://dev.to/hassanzohdy/collections-your-ultimate-javascript-array-handler-3o15)
* [Supportive Is: an elegant utility to check types of values in JavaScript](https://dev.to/hassanzohdy/supportive-is-an-elegant-utility-to-check-types-of-values-in-javascript-1b3e)
* [Localization: An agnostic i18n package to manage localization in your project](https://dev.to/hassanzohdy/mongez-localization-the-simplest-way-to-translate-your-website-regardless-your-favorite-framework-4gi3)

 React Js Packages

* [useFetcher: easiest way to fetch data in React Js](https://dev.to/hassanzohdy/usefetcher-the-easiest-way-to-fetch-data-in-react-45o9)

Courses (Articles)

* [React Js: Let"s Create File Manager With React Js and Node Js](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)