Check Shopify webhook signature with NestJs

Check Shopify webhook signature with NestJs

Are you having a hard time checking shopify webhook signature with NestJs? Here’s why and how to solve it!

NestJs has automatically parses the request body into JSON. This is very useful in most of applications, but some times you need to access to the raw request body, for example when you have to verify the signature from a Shopify webhook request. You should disable the NestJs JSON parser, but probably you need it in your application, without it the validation pipes would not work.

Here’s how to solve it without breaking your whole application.

We are going to create 2 middlewares, one to parse body and one to get it raw.

STEP 1 – Disable NestJS automatic body parsing

In your main.ts add the option { bodyParser: false } in the app creation:

// main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bodyParser: false })
  await app.listen(3000)
}

bootstrap()

Now you application could be broken – no problem we are going to fix later!

STEP 2 – Create the middlewares

As we said we are going to create a middleware to parse the body and a middleware to get it raw

Create a new file JsonBodyMiddleware.ts

// JsonBodyMiddleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common'
import { json } from 'body-parser'

@Injectable()
export class JsonBodyMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => any) {
    json()(req as any, res as any, next)
  }
}

Then create a new file RawBodyMiddleware.ts

// RawBodyMiddleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response } from 'express';
import * as bodyParser from 'body-parser';

@Injectable()
export class RawBodyMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => any) {
    bodyParser.raw({type: '*/*'})(req, res, next);
  }
}

We have our middlewares. How are we going to use them?

Step 3 – Apply the middlewares

Now we apply the middlewares and our app.module.ts is this

//app.module.ts

import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { RawBodyMiddleware } from "./RawBodyMiddleware";
import { JsonBodyMiddleware } from "./JsonBodyMiddleware";
import { RouteInfo } from "@nestjs/common/interfaces";

@Module({
  imports: [],
})
export class AppModule implements NestModule {

  private rawBodyParsingRoutes: Array<RouteInfo> = [
    {
      path: 'webhook/endpoint',
      method: RequestMethod.POST,
    },
  ]

  public configure(consumer: MiddlewareConsumer): MiddlewareConsumer | void {
    consumer
      .apply(RawBodyMiddleware)
      .forRoutes(...this.rawBodyParsingRoutes)
      .apply(JsonBodyMiddleware)
      .exclude(...this.rawBodyParsingRoutes)
      .forRoutes('*')
  }
}

But what does it mean? We are instructing NestJs to use the raw body only for the routes we are defining in rawBodyParsingRoutes. In all the remaining routes it will use the Json Body Parser. Now the last step.

STEP 4 – verify shopify signature

We get raw body using @Req decorator as paramenter and then req.body.toString()

//app.controller.ts

import {
  Controller,
  Post,
  Req,
} from '@nestjs/common';
import crypto from "crypto";


@Controller('webhook')
export class AppController {
 
  @Post('endpoint')
  async orderPaid(@Req() req) {
    
    const apiKey = process.env.SHOPIFY_WEBHOOK_API_KEY

    const hash = crypto
      .createHmac('sha256', apiKey)
      .update(req.body.toString())
      .digest('base64')

    if(req.headers['x-shopify-hmac-sha256']!==hash) {
      throw new UnauthorizedException()
    }
    else {
      console.log('Signature is valid')
    }
    

  }

}

Leave a Reply

Your email address will not be published. Required fields are marked *