Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failed WhatsApp Signature Validation #815

Open
florianmaxim opened this issue Jun 22, 2020 · 23 comments
Open

Failed WhatsApp Signature Validation #815

florianmaxim opened this issue Jun 22, 2020 · 23 comments
Assignees
Labels

Comments

@florianmaxim
Copy link

florianmaxim commented Jun 22, 2020

Hey there!
Thank you for this great project!
Unfortunalety I'm having trouble connecting twilio (live credentials) to my whatsapp webhook (other webhooks work great)..
Twilio is trowing an "11200" errorCode containing this body:

{ "error": { "message": "WhatsApp Signature Validation Failed!", "request": { "rawBody": "xxx", "headers": { "x-twilio-signature": "xxxLzNwapQY4rGkb+mVFYO2I2jI=" } } } }

Any help would would be appreciated!

@chentsulin
Copy link
Collaborator

@chentsulin chentsulin self-assigned this Jun 22, 2020
@florianmaxim
Copy link
Author

florianmaxim commented Jun 22, 2020

Hm, do you maybe see any issue in my nginx handler ?

`location /api/ {

  proxy_pass_header Server;

  proxy_set_header Host $http_host;

  proxy_set_header X-Scheme $scheme;

  proxy_set_header X-Real-IP	$remote_addr;

  proxy_set_header X-Forwarded-For	$proxy_add_x_forwarded_for;

  proxy_redirect off;

  proxy_connect_timeout 20;

  proxy_read_timeout 20;

  proxy_pass https://nodejs:3000/;

}

`

@florianmaxim
Copy link
Author

@chentsulin Ah, wait it always comes with a second error:

ErrorUrl "https://xxxxxx.dev/api/webhooks/whatsapp"
SmsMessageSid "SM849483eda06exxxxxx"
ErrorCode "11200"
NumMedia "0"
SmsSid "SM849483eda06e945xxxxxx"
SmsStatus "received"
Body "hallo"
To "whatsapp:+14155xxxxxx"
NumSegments "1"
MessageSid "SM849483eda06e945d69exxxxxx"
AccountSid "ACd5227f8efd4687bb2xxxxxx"
From "whatsapp:+491764xxxxxx"
ApiVersion "2010-04-01"
sourceComponent "14100"
url "null"
ErrorCode "11200"
LogLevel "ERROR"
Msg "The URI scheme, of the URI null, must be equal (ignoring case) to 'http', 'https', 'ws', or 'wss'"
EmailNotification "false"

@chentsulin
Copy link
Collaborator

chentsulin commented Jun 22, 2020

For the first error, if you're using express behind nginx you may want to enable('trust proxy'), so your server can use the X-Forwarded-* headers to determine the connection and the IP address of the client:
https://stackoverflow.com/questions/39930070/nodejs-express-why-should-i-use-app-enabletrust-proxy

For the second error, are you sending any urls (for example, media url) in your app? could you show me the url or the function call?

@chentsulin
Copy link
Collaborator

Oh, maybe I should make some changes to the inferred url:

url: `https://${req.headers.host}${req.url}`,

And still need another two nginx configs to make it works:

https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/

X-Forwarded-Host: example.com
X-Forwarded-Proto: https

@florianmaxim
Copy link
Author

florianmaxim commented Jun 22, 2020

The server app is as reduced as this:
` const server = express();

server.set('trust proxy');

const verify = (req, _, buf) => {
req.rawBody = buf.toString();
};
server.use(bodyParser.json({ verify }));
server.use(bodyParser.urlencoded({ extended: false, verify }));

server.all('*', (req, res) => {
return handle(req, res);
});`

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

Thank you so much for the update!

I updated bottender to 1.4.7 as well as the NGINX server block:

`
proxy_pass http://nodejs:3000/;

  proxy_set_header    X-Real-IP           $remote_addr;

  proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;

  proxy_set_header    X-Forwarded-Proto   $scheme;

  proxy_set_header    Host                $host;

  proxy_set_header    X-Forwarded-Host    $host;

  proxy_set_header    X-Forwarded-Port    $server_port;

`

But still getting the same error from f*** Twilio :(
This is frustrating since everthing is working so well with th eother platforms!

Any other hints?..

@chentsulin
Copy link
Collaborator

chentsulin commented Jun 23, 2020

Are you able to print this out on your express server? This might helps a lot.

const server = express();

server.use((req, res, next) => {
  console.log('headers', JSON.stringify(req.headers));
  console.log('hostname', req.hostname);
  console.log('protocol', req.protocol);
  console.log('requestUrl', `${req.protocol}://${req.hostname}${req.url}`);

  next();
});

We should make sure the requestUrl here in your server match the url you registered on Twilio.

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

here is the log:

`"headers": {
"x-real-ip":"54.242.136.XXX",
"x-forwarded-for":"54.242.136.XXX",
"x-forwarded-proto":"https",
"host":"hn-bot.dev",
"x-forwarded-host":"XXX.dev",
"x-forwarded-port":"443",
"connection":"close",
"content-length":"318",
"content-type":"application/x-www-form-urlencoded",
"x-twilio-signature":"9gsseAIhctS18mbe7dErVXXXXXXXXX",
"i-twilio-idempotency-token":"8f13d9ea-4884-4b81-8dd9-7XXXXXXXXX",
"accept":"/",
"cache-control":"max-age=259200",
"user-agent":"TwilioProxy/1.1"
}"

hostname: XXX.dev
protocol: http
requestUrl http://XXX.dev/webhooks/whatsapp`

So I guess "x-forwarded-proto":"https" and protocol: http should match, right?

@chentsulin
Copy link
Collaborator

chentsulin commented Jun 23, 2020

It's a little bit weird.
I expected express req.protocol should return the value of x-forwarded-proto when trust proxy mode is on.

I see your comment calling server.set('trust proxy'); here:
#815 (comment)

If you did that, you should change that line to one of the following call:

server.set('trust proxy', true);

// or
server.enable('trust proxy');

Those two do the exactly same thing. See: https://github.com/expressjs/express/blob/18da651c5b57431f61391d9de2518a8c2ef88b2c/lib/application.js#L452-L454

But you can't call

server.set('trust proxy');

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

@chentsulin Ah, damn it, my bad! I changed it and now it matches:

"x-forwarded-proto":"https" and protocol: https matches.

But I just realized the url ending /api/ is getting lost on it's way!

requestUrl http://XXX.dev/webhooks/whatsapp should actually be requestUrl http://XXX.dev/api/webhooks/whatsapp

Any idea where this is happening?

@chentsulin
Copy link
Collaborator

chentsulin commented Jun 23, 2020

Could req.originalUrl help in this case?

const server = express();

server.use((req, res, next) => {
  console.log('originalUrl', req.originalUrl);
  console.log('url', req.url);
  console.log('requestUrl', `${req.protocol}://${req.hostname}${req.originalUrl}`);

  next();
});

If /api is included in req.originalUrl, we should switch to use it.

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

I already tried. originalUrl /webhooks/whatsapp doesnt include the path as well..

It is not supposed to be included in the bottender.config.js, or is it?

whatsapp: { enabled: true, path: '/webhooks/whatsapp',

@tw0517tw
Copy link
Collaborator

How about adding $request_uri in your nginx config proxy_pass part?
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
圖片

@tw0517tw
Copy link
Collaborator

Note: you might need to modify path in your bottender.config.js accordingly

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

How about adding $request_uri in your nginx config proxy_pass part?
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
圖片

It's already inside a location block:

location /api/ { proxy_pass http://nodejs:3000;

So I'm relatively sure I don't need the $request_uri in here, or do I?

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

@tw0517tw

Ah, it has to be included on the proxy header as well, ofc!

proxy_set_header Host $host$request_uri;
proxy_set_header X-Forwarded-Host $host$request_uri;

EDIT: Which is actually not a soluction, since modifiying the host like that doesnt realy make sense...

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

Okay, maybe this helps:

Since it's all working FINE for other messengers here's the slack config:

The Slack request URL is set to https://xxx.dev/api/webhooks/slack.

Path in bottender.config.js is set to /webhooks/messenger'.

Enabled trust proxy server.enable('trust proxy');.

Log of a slack request:

hostname xxx.dev
protocol https
originalUrl /webhooks/slack
requestUrl https://xxx.dev

NGINX block:

location /api/ {

      proxy_pass http://nodejs:3000/;

      proxy_set_header    X-Real-IP                   $remote_addr;

      proxy_set_header    X-Forwarded-For      $proxy_add_x_forwarded_for;

      proxy_set_header    X-Forwarded-Proto   $scheme;

      proxy_set_header    Host                          $host;

      proxy_set_header    X-Forwarded-Host    $host;

      proxy_set_header    X-Forwarded-Port    $server_port;

}

Why is this not working for twilio?

@chentsulin
Copy link
Collaborator

chentsulin commented Jun 23, 2020

@florianmaxim

Twilio is the only provider that uses HTTP URL to sign the signature.
I'm thinking the graceful way to solve this url behind proxy issue, but if you want to work around this, you may try this for now:

const server = express();

server.use((req, res, next) => {
  req.url = `/api${req.url}`; // conditionally apply this on your proxy env

  next();
});

Edit: Sorry my bad. Should be /api${req.url} instead of /api/${req.url}

@chentsulin
Copy link
Collaborator

Not everyone have permission to change the settings of nginx, so maybe we should support some other options to config host, protocol and url just like twilio-node does:

https://github.com/twilio/twilio-node/blob/565cc4cc523ab7fe7efa3a9eb2dcc73bfa55e759/lib/webhooks/webhooks.js#L211-L214

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

@florianmaxim

Twilio is the only provider that uses HTTP URL to sign the signature.
I'm thinking the graceful way to resolve this url behind proxy issue, but if you want to work around this, you may try this for now:

const server = express();

server.use((req, res, next) => {
  req.url = `/api${req.url}`; // conditionally apply this on your proxy env

  next();
});

Edit: Sorry my bad. Should be /api${req.url} instead of /api/${req.url}

That indeed turn the log console.log('requestUrl', ${req.protocol}://${req.hostname}${req.url});
into https://xxx.dev/api/webhooks/whatsapp.

But results into a 502 on Twilio:

Twilio was unable to fetch content from: http://xxx.dev/api/webhooks/whatsapp
Error: Total timeout is triggered. Configured tt is 15000ms and we attempted 1 time(s)
Account SID: ACd5227f8efd4687bb211da6e5xxxxxx
SID: SM5f8958810d5a3c070db2f889fxxxxxx
Request ID: 5864b582-3ca8-41bd-923e-5ed38xxxxxx
Remote Host: xxx.dev
Request Method: POST
Request URI: http://xxx.dev/api/webhooks/whatsapp
URL Fragment: true

:/ :(

@chentsulin
Copy link
Collaborator

After adding that path patch, you need to modify path in your bottender.config.js accordingly. For example:

whatsapp: { enabled: true, path: '/webhooks/whatsapp',

to

whatsapp: { enabled: true, path: '/api/webhooks/whatsapp',

Note: This affects every channel (messenger, whatsapp...).

@florianmaxim
Copy link
Author

florianmaxim commented Jun 23, 2020

After adding that path patch, you need to modify path in your bottender.config.js accordingly. For example:

whatsapp: { enabled: true, path: '/webhooks/whatsapp',

to

whatsapp: { enabled: true, path: '/api/webhooks/whatsapp',

Note: This affects every channel (messenger, whatsapp...).

Right, of course! This works.

So briefly summarized:

server.use((req, res, next) => {
    if(req.originalUrl === '/webhooks/whatsapp') {
      req.url = `/<url_path>${req.url}`; 
    }
}):
whatsapp: {
      enabled: true,
      path: '/<url_path>/webhooks/whatsapp',
}

Thanks a lot to @tw0517tw @chentsulin for this quick workaround!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants