问题
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...
回答1:
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!
回答2:
For anyone looking for a more elegant solution, turn off the bodyParser
in main.ts
Create 2 middlewares
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);
}
}
And apply the middlewares to appropriate routes in app.module.ts
.
app.module.ts
...
public configure(consumer: MiddlewareConsumer): void {
consumer
.apply(RawBodyMiddleware)
.forRoutes({
path: '/stripe-webhooks',
method: RequestMethod.POST,
})
.apply(JsonBodyMiddleware)
.forRoutes('*');
}
BTW req.rawbody
has been removed from express
long back - https://github.com/expressjs/express/issues/897
回答3:
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
回答4:
This is because the bodyParser
middleware is used by default and has already consumed the body when your middleware starts. You can turn off bodyParser
in your main.ts
:
const app = await NestFactory.create(AppModule, {bodyParser: false});
^^^^^^^^^^^^^^^^^^
You should note though, that in most other cases you will probably want to use bodyParser.json
so add this to all the other routes. You can exclude one particular route from middleware by using a negative regex, see e.g. this thread.
来源:https://stackoverflow.com/questions/54346465/access-raw-body-of-stripe-webhook-in-nest-js