public

Adding context to nest.js requests

Do you need to know who requests are for or need to "enrich" the request before your controller is called? Typically you would add logic to the method

Latest Post Speed kills software engineering. by Matthew Davis public

Do you need to know who requests are for or need to "enrich" the request before your controller is called?

Typically you would add logic to the method that is called after routes are permitted such as seeing if the user has a valid JWT token or has the right permissions. This common implementation poses several problems. Why should the route even call your method if certain conditions aren't met? And, why litter your code base with duplicate code in every route just to validate things like user permissions and roles?

Enrich your requests by adding context and securing your requests at the same time by using both a ParameterDecorator and Guard.

The idea is to be able to add a decorator to your route method and have it auto-magically give you additional context beyond what the request payload is such as a users profile, permissions, etc.

Setup

We need to only create two new typescript files in your src/ directory:

  1. principal-guard.ts
  2. principal-decorator.ts

The route guard

Add the logic that will add your contextual information to the request object itself which is then accessed using a parameter decorator (next step):

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Request } from 'express';
import { User } from './user';

/**
 * Principal Guard for protecting routes and automatically retrieving the users profile.
 */
@Injectable()
export class PrincipalGuard implements CanActivate {

    /**
     * Called before a route is executed.
     *
     * @param {ExecutionContext} context
     * @returns {Promise<boolean>}
     */
    public async canActivate(context: ExecutionContext): Promise<boolean> {

        const ctx = context.switchToHttp();
        const request = ctx.getRequest<Request>();

        if (request.headers.authorization) {

            const split = request.headers.authorization.split(' ');

            try {

                //
                // Verify and decode the JWT token.
                //
                // const decoded = jwt.verify(split[1], process.env.JWT_SECRET);
                const decoded = {
                    id: 123
                };

                //
                // Call your database or whatever to get the user by the id from
                // the jwt token (decoded['id']).
                //
                const user: User = {
                    id: decoded['id'],
                    email: 'matthew@matthewdavis.io',
                    website: 'https://matthewdavis.io',
                    secret: 'super'
                };

                //
                // Assuming that the user is found, set the user on the request.
                //
                if (user) {
                    request['principal'] = user;
                    return true;
                }

            } catch (e) {
                return false;
            }
        }
    }
}
principal-guard.ts

The decorator

Create the parameter decorator so we can add it to our route method parameter to "inject" the users profile from above:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

/**
 * Custom decorator for adding principal to request object.
 *
 * @type {(...dataOrPipes: Type<PipeTransform> | PipeTransform | any[]) => ParameterDecorator}
 */
export const Principal = createParamDecorator((data: string, context: ExecutionContext) => {
    return context.switchToHttp().getRequest().principal;
});
principal-decorator.ts

Tying it all together now

Simply add the @UseGuards(PrincipalGuard) to your controller or method(s) and then decorate a parameter using @Principal():

import { Controller, Get, UseGuards } from '@nestjs/common';
import { Principal } from './principal';
import { PrincipalGuard } from './principal-guard';
import { User } from './user';

@Controller()
export class AppController {

    @Get()
    @UseGuards(PrincipalGuard)
    public test(@Principal() principal: User): User {
        return principal;
    }
}
app.controller.ts

Testing it out!

curl -sH 'Authorization: Bearer somejwttokenhere' \ 
    http://localhost:3000 | jq
test shell command

See also

Matthew Davis

Published a year ago