Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Secure firebase webhook

I have to create a webhook from typeform to firebase. I will create a cloud function listening to events sent from typeform. The typeform is managed by a third party.

The only issue I have, is the authorization part for the webhook. I understood (from reading different post) that anyone can "talk" to the cloud function URL. But I would like to have a secure and exclusive communication between typeform and firebase.

Any hints ?

Thank for your time.

like image 674
MMM14 Avatar asked Oct 30 '25 07:10

MMM14


2 Answers

You can definitively connect a Typeform webhook to a Cloud function and push data to Firebase storage.

In addition to authentication pointed by Frank, Typeform also provides a signature mechanism to ensure that the request comes from Typeform webhook.

Typeform lets you define a secret to sign the webhook payload.

When you receive the payload on your end, in the cloud function, you verify first if it's signed correctly, if it's not it means it's not coming from Typeform, therefore, you should not deal with it.

Here is an example to verify the webhook signature:

app.post('/typeform/webhook', async (request, response) => {
  console.log('~> webhook received');
  // security check, let's make sure request comes from typeform
  const signature = request.headers['typeform-signature']
  const isValid = verifySignature(signature, request.body.toString())
  if (!isValid) {
    throw new Error('Webhook signature is not valid, someone is faking this!');  
  }
      
  //valid signature let's do something with data received
})

And here is the verifySignature function

const crypto = require('crypto')
const verifySignature = function(receivedSignature, payload){
  const hash = crypto
            .createHmac('sha256', webhookSecret)
            .update(payload)
            .digest('base64')
  return receivedSignature === `sha256=${hash}`
}

There are more details on Typeform documentation.

Hope it helps :)

like image 130
Nicolas Grenié Avatar answered Nov 01 '25 21:11

Nicolas Grenié


Calling request.body.toString() does not work the way it is described in @Nicolas Greniés answer. The result will always be the string "[Object object]", as it only utilizes the default prototype as described here (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString).

A valid approach to stringify req.body would be to use JSON.stringify() which would still not deliver the expected result as you need to hash the original binary data (https://developer.typeform.com/webhooks/secure-your-webhooks/).

The Solution (without Firebase)

Use app.use(bodyParser.raw({ type: 'application/json' })) as specified here (Validate TypeForm Webhook payload in Node) to get the raw binary data and pass the request body directly into the hashing function.

const bodyParser = require("body-parser");

app.use(bodyParser.raw({ type: "application/json" })); // Notice .raw !

app.post("/typeform-handler", (req, res) => {
      const hash = crypto
            .createHmac('sha256', MY_TYPEFORM_SECRET)
            .update(req.body) // Pass the raw body after getting it using bodyParser
            .digest('base64')
})

The solution using Firebase

If you are using a Firebase Cloud Function to handle the request, you can't use bodyParser this way as Firebase already takes care of the parsing (https://firebase.google.com/docs/functions/http-events#read_values_from_the_request). Instead, use req.rawBody to access the raw body and pass it to the hash function.

// No need for bodyParser

app.post("/typeform-handler", (req, res) => {
      const hash = crypto
            .createHmac('sha256', MY_TYPEFORM_SECRET)
            .update(req.rawBody) // Notice .rawBody instead of just .body
            .digest('base64')
})

Remark for TypeScript users

The default Express Request object does not contain a rawBody property. Be aware that TypeScript therefore might throw an error of no overload matches this call or Property 'rawBody' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'. The actual Request object will however be provided by Firebase and will contain said properties. You can access the actual Request object type using functions.https.Request.

like image 25
aside Avatar answered Nov 01 '25 21:11

aside