Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access raw body of Stripe webhook in Nest.js

Tags:

I need to access the raw body of the webhook request from Stripe in my Nest.js application.

Following this example, I added the below to the module which has a controller method that is needing the raw body.

function addRawBody(req, res, next) {   req.setEncoding('utf8');    let data = '';    req.on('data', (chunk) => {     data += chunk;   });    req.on('end', () => {     req.rawBody = data;      next();   }); }  export class SubscriptionModule {   configure(consumer: MiddlewareConsumer) {     consumer       .apply(addRawBody)       .forRoutes('subscriptions/stripe');   } } 

In the controller I am using @Req() reqand then req.rawBody to get the raw body. I need the raw body because the constructEvent of the Stripe api is using it to verify the request.

The problem is that the request is stuck. It seems that the req.on is not called either for data nor for the end event. So next() is not called in the middleware.

I did also try to use raw-body like here but I got pretty much the same result. In that case the req.readable is always false, so I am stuck there as well.

I guess this is an issue with Nest.js but I am not sure...

like image 779
rablentain Avatar asked Jan 24 '19 12:01

rablentain


People also ask

How do you get the stripe Webhook secret?

Before you can verify signatures, you need to retrieve your endpoint's secret from your Dashboard's Webhooks settings. Select an endpoint that you want to obtain the secret for, then click the Click to reveal button. Stripe generates a unique secret key for each endpoint.

Is NestJS open source?

NestJS is an open-source, extensible, versatile, progressive Node. Js framework for creating compelling and demanding backend systems. It's currently the fastest-growing Node. Js framework in TypeScript.

How to restrict only for stripe webhooks in JSDoc?

configure the app with preserveRawBodyInRequest as shown in JSDoc example (to restrict only for stripe webhook use "stripe-signature" as filter header) use RawBody decorator in handler to retrieve the raw (text)body

How does stripe work with webhooks?

It was either by requesting the Stripe API directly on the frontend, or the backend. With webhooks, Stripe can communicate with us the other way around. Webhook is a URL in our API that Stripe can request to send us various events such as information about payments or customer updates.

Why do I need the raw body of a stripe request?

I need the raw body because the constructEvent of the Stripe api is using it to verify the request. The problem is that the request is stuck. It seems that the req.on is not called either for data nor for the end event. So next () is not called in the middleware. I did also try to use raw-body like here but I got pretty much the same result.

How to consume nextjs request body as stream?

By default, NextJS parses the the request body based upon the incoming Content-Type in the headers. You would want to disable this [0] and then consume it as a stream using buffer.


2 Answers

For anyone looking for a more elegant solution, turn off the bodyParser in main.ts. Create two middleware functions, one for rawbody and the other for json-parsed-body.

json-body.middleware.ts

import { Request, Response } from 'express'; import * as bodyParser from 'body-parser'; import { Injectable, NestMiddleware } from '@nestjs/common';  @Injectable() export class JsonBodyMiddleware implements NestMiddleware {     use(req: Request, res: Response, next: () => any) {         bodyParser.json()(req, res, next);     } } 

raw-body.middleware.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);     } } 

Apply the middleware functions to appropriate routes in app.module.ts.

app.module.ts

[...]  export class AppModule implements NestModule {     public configure(consumer: MiddlewareConsumer): void {         consumer             .apply(RawBodyMiddleware)             .forRoutes({                 path: '/stripe-webhooks',                 method: RequestMethod.POST,             })             .apply(JsonBodyMiddleware)             .forRoutes('*');     } }  [...] 

And tweak initialization of Nest to turn off bodyParser:

main.ts

[...]  const app = await NestFactory.create(AppModule, { bodyParser: false })  [...] 

BTW req.rawbody has been removed from express long ago.

https://github.com/expressjs/express/issues/897

like image 51
Joel Raju Avatar answered Sep 17 '22 09:09

Joel Raju


I ran into a similar problem last night trying to authenticate a Slack token.

The solution we wound up using did require disabling the bodyParser from the core Nest App then re-enabling it after adding a new rawBody key to the request with the raw request body.

    const app = await NestFactory.create(AppModule, {         bodyParser: false     });      const rawBodyBuffer = (req, res, buf, encoding) => {         if (buf && buf.length) {             req.rawBody = buf.toString(encoding || 'utf8');         }     };      app.use(bodyParser.urlencoded({verify: rawBodyBuffer, extended: true }));     app.use(bodyParser.json({ verify: rawBodyBuffer }));  

Then in my middleware I could access it like so:

const isVerified = (req) => {     const signature = req.headers['x-slack-signature'];     const timestamp = req.headers['x-slack-request-timestamp'];     const hmac = crypto.createHmac('sha256', 'somekey');     const [version, hash] = signature.split('=');      // Check if the timestamp is too old     // tslint:disable-next-line:no-bitwise     const fiveMinutesAgo = ~~(Date.now() / 1000) - (60 * 5);     if (timestamp < fiveMinutesAgo) { return false; }      hmac.update(`${version}:${timestamp}:${req.rawBody}`);      // check that the request signature matches expected value     return timingSafeCompare(hmac.digest('hex'), hash); };  export async function slackTokenAuthentication(req, res, next) {     if (!isVerified(req)) {         next(new HttpException('Not Authorized Slack', HttpStatus.FORBIDDEN));     }     next(); } 

Shine On!

like image 27
MikingTheViking Avatar answered Sep 20 '22 09:09

MikingTheViking