Access raw body of Stripe webhook in Nest.js
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.
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!
Today,
as I am using NestJS and Stripe
I installed body-parser (npm),then in the main.ts,just add
app.use('/payment/hooks', bodyParser.raw({type: 'application/json'}));
and it will be restricted to this route ! no overload