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() req
and 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...
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.
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.
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
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.
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.
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.
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
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With