Skip to content

Instantly share code, notes, and snippets.

@alasano
Last active December 11, 2024 07:08
Show Gist options
  • Select an option

  • Save alasano/c66f6e5c03518306ba94cf2ea4617bfc to your computer and use it in GitHub Desktop.

Select an option

Save alasano/c66f6e5c03518306ba94cf2ea4617bfc to your computer and use it in GitHub Desktop.
NestJS Slack Signature Verification - Drop this guard into any controller which needs to verify the authenticity of requests coming from Slack! @UseGuards(SlackGuard) - Based on https://api.slack.com/docs/verifying-requests-from-slack
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import * as qs from 'qs';
import * as crypto from 'crypto';
import { Buffer } from 'buffer';
@Injectable()
export class SlackGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean {
const { headers, body } = context.switchToHttp().getRequest();
const hmac = crypto.createHmac('sha256', process.env.SLACK_SIGNING_SECRET)
const rawBody = qs.stringify(body).replace(/%20/g, "+");
const slackSignature = headers['x-slack-signature'];
const requestTimestamp = headers['x-slack-request-timestamp'];
// ~~ is equivalent to Math.floor()
const timeInSeconds = ~~(new Date().getTime() / 1000);
// Reject if request is older than 5 minutes
if (Math.abs(timeInSeconds - requestTimestamp) > 300) return false;
const signatureBaseString = `v0:${requestTimestamp}:${rawBody}`;
const mySignature = 'v0=' + hmac.update(signatureBaseString, 'utf8').digest('hex');
return crypto.timingSafeEqual(
Buffer.from(mySignature, 'utf8'),
Buffer.from(slackSignature, 'utf8')
);
}
}
@El-Fitz

El-Fitz commented Aug 9, 2021

Copy link
Copy Markdown

Hi!

Just wanted to let you know, after getting through a bit of trouble myself, that qs.stringify(body, { format: 'RFC1738' }), although it properly decodes the body, will make the signature verification fail if the body contains %28 (() or %29 ()). Seems like qs.stringify(body).replace(/%20/g, "+") solves it and otherwise works just as well.

@alasano

alasano commented Aug 29, 2021

Copy link
Copy Markdown
Author

Hey @El-Fitz!

That's an interesting discovery! I've tested it and it seems to be good, it's only about replacing spaces with "+" after all.

Glad to see that people are stumbling upon this bit of code otherwise. Merci!

@El-Fitz

El-Fitz commented Aug 30, 2021

Copy link
Copy Markdown

Hi @alasano!
You're welcome! Your gist helped me tremendously ^^

@gboudreau

Copy link
Copy Markdown

For me, const rawBody = qs.stringify(body).replace(/%20/g, "+"); didn't work, because I am receiving JSON-formatted requests from Slack, and thus need to use the request.rawBody as received (in JSON) to calculate the signature.

I did this instead:

    const request = context.switchToHttp().getRequest<RawBodyRequest<Request>>();
    // ...
    const rawBody = request.rawBody?.toString('utf8') as string;

@HBLAB-TruongTT

Copy link
Copy Markdown

You are my savier!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment