Using HMAC Authentication Type

HMAC is a specific type of authentication code involving a cryptographic hash function and a secret key. It may be used to simultaneously verify both the data integrity and the authentication of a message, as with any MAC.

Descope allows you to use HMAC to sign the payload of your HTTP Connector. The outcome signature will be sent in the x-descope-webhook-s256 header. The recipient service should use this secret to validate the payload's integrity and authenticity by verifying the supplied signature.

Validating the HMAC Signature

To validate the HMAC signature, the code could look something like this:

index.js
import { RawBodyRequest } from '@nestjs/common';
import crypto from 'crypto';
import { Request } from 'express';
 
function validate(raw: RawBodyRequest<Request>): boolean {
    const hmac = crypto.createHmac('sha256', process.env.HMAC_SECRET_KEY);
    hmac.update(Buffer.from(raw.rawBody)); // Ensure rawBody is a Buffer
    hmac.end();
    const calculated = hmac.read().toString('base64');
    const signature = raw.get('x-descope-webhook-s256') ?? 'invalid or nonexisting signature';
 
    // Convert both the calculated signature and the received signature to Buffers
    const signatureBuffer = Buffer.from(signature, 'base64');
    const calculatedBuffer = Buffer.from(calculated, 'base64');
 
    // Ensure both Buffers are of the same length
    if (signatureBuffer.length !== calculatedBuffer.length) {
        return false;
    }
 
    return crypto.timingSafeEqual(signatureBuffer, calculatedBuffer);
}

Or this:

index.js
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
 
const app = express();
const PORT = 3000;
 
// Middleware to parse JSON payloads
app.use(bodyParser.json());
 
function verifyHmacSignature(payload, secret, sentHmac) {
 const computedHmac = crypto
   .createHmac('sha256', secret)
   .update(JSON.stringify(payload))
   .digest('base64');
 
 return sentHmac === computedHmac;
}
 
app.post('/webhook-endpoint', (req, res) => {
 const payload = req.body; // This is the parsed body
 const headers = req.headers; // This contains all headers
 
 // Assuming the sent HMAC is transmitted in the header 'x-hmac-signature'
 const sentHmac = headers['x-descope-webhook-s256'];
 
 // Use your secret here
 const secret = 'YOUR_SECRET';
 
 if (!verifyHmacSignature(payload, secret, sentHmac)) {
     res.status(403).send('Invalid HMAC');
 }
 
 // Serve request
});
 
app.listen(PORT, () => {
 console.log(`Server is listening on port ${PORT}`);
});

Mocking an HMAC Signature

If you wish to test the HMAC signature validation, you can use the following code to generate a valid signature for a given payload and secret, then include it in the headers. Note that Descope creates the HMAC signature from a JSON string, not the raw post body.

test.js
import fetch from 'node-fetch';
import crypto from 'crypto';
 
    it('HMAC Signature', async () => {
      fetch.mockResolvedValue({
        ok: true,
        json: () => Promise.resolve({}),
      });
 
      const hmacSecret = 'YOUR_SECRET';
      const payload = {
        p1: 'v1',
      };
 
      await handler({
        command: 'post',
        configuration: {
          baseUrl: 'https://example.com',
          hmacSecret,
        },
        args: {
          endpoint: '/api',
          payload,
        },
      });
 
      // ensure that the signature is correct
      const expectedSignature = crypto
        .createHmac('sha256', hmacSecret)
        .update(JSON.stringify(payload))
        .digest('base64');
 
      expect(fetch).toHaveBeenCalledWith(
        'https:/example.com/api',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify({
            p1: 'v1',
          }),
          headers: expect.objectContaining({
            'x-descope-webhook-s256': expectedSignature,
          }),
        }),
      );
    });
Was this helpful?

On this page