'Using a Zapier Custom Request Webhook with JSON Web Tokens

I need to access an API that uses JSON Web Tokens as their authentication method. Is there a good way to use a python code step to create this token then add that token as a header to a custom request webhook step?



Solution 1:[1]

My experience authenticating with APIs has been using the simple API key method. As such I first read your question and didn't fully understand. I decided to do some research and hopefully learn something along the way, and I certainly did. I share my findings and answer below:

For starters I began reading into JSON Web Tokens(JWT) which lead me to the JWT website, which was an excellent resource. It very clearly spells out the components that make up a JWT and how they need to be formatted, I would highly recommend having a look.

From the JWT website I found that a JWT is made up of three components:

  1. A base64 URL safe encoded header.
  2. A base64 URL safe encoded payload.
  3. A base64 URL safe encoded signature.

All three of the above combined form the correctly formatted JWT. Fortunately the JWT website has a list of libraries made for Python. Unfortunately none of these third-party libraries are available in the vanilla Python offered by the Zapier code module. To get this done required reading some source code and leveraging what libraries we do have available. So after a few hours and lots of trial and error I was able to come up with the following solution for generating a correctly formatted JWT:

import hashlib
import hmac
import requests
from base64 import urlsafe_b64encode

def base64url_encode(payload):
  if not isinstance(payload, bytes):
    payload = payload.encode('utf-8')
  encode = urlsafe_b64encode(payload)
  return encode.decode('utf-8').rstrip('=')

def generate_JWT(header, payload, secret):

  encoded_header = base64url_encode(header)
  encoded_payload = base64url_encode(payload)

  signature = hmac.new(secret,
                      encoded_header + "." + encoded_payload, 
                      hashlib.sha256)
  encoded_signature = base64url_encode(signature.digest())

  return encoded_header + "." + encoded_payload + "." + encoded_signature

def get_request(url, jwt):

  headers = {
    "Authorization" : "Bearer " + jwt
  }

  result = requests.get(url, headers=headers)
  return result

secret = "yoursecrettoken"
header = '{"alg":"HS256","typ":"JWT"}'
payload = '{"sub":"1234567890","name":"John Doe","iat":1516239022}'
jwt = generate_JWT(header, payload, secret)

response = get_request("https://SomeApiEndpoint.com/api/", jwt)

You can test the output of this against the JWT's debugger here.

Note: For the encoding to work properly for the header and payload objects you have to convert them to a string object. I tried doing this by calling the JSON.dumps() function and passing the dictionary objects, but when I encoded the return values they did not match what was shown on the JWT debugger. The only solution I could find was by wrapping the dictionary objects in quotations and ensuring there were no spaces within it.

And so with the JWT in hand you can use it in your Zapier Webhooks custom get request step, or you could save the zap and send the request in the same code module using Python's request library as I have in my code example.

Thanks for the learning opportunity, and I hope this helps.

Solution 2:[2]

I had a similar issue trying to generate and use a JWT in a custom integration. Unfortunately, this code above did not work for my situation. I'm currently using the below javascript and it seems to be functioning perfectly.

const toBase64 = obj => {
    const str = JSON.stringify (obj);
    return Buffer.from(str).toString ('base64');
 };
 
 const replaceSpecialChars = b64string => {
 // this will match the special characters and replace them with url-safe substitutes
   return b64string.replace (/[=+/]/g, charToBeReplaced => {
     switch (charToBeReplaced) {
       case '=':
         return '';
       case '+':
         return '-';
       case '/':
         return '_';
     }
   });
 };

const crypto = require('crypto');
const signatureFunction = crypto.createSign('RSA-SHA256');

const headerObj = {
    alg: 'RS256',
    typ: 'JWT',
};

const payloadObj = {
    iat: Math.round(Date.now() / 1000),   // lists the current Epoch time
    exp: Math.round(Date.now() / 1000) + 3600,  // adds one hour
    sub: '1234567890'
    name: 'John Doe'
    
};

const base64Header = toBase64(headerObj);
const base64Payload = toBase64(payloadObj);

const base64UrlHeader = replaceSpecialChars(base64Header);
const base64UrlPayload = replaceSpecialChars(base64Payload);

signatureFunction.write(base64UrlHeader + '.' + base64UrlPayload);
signatureFunction.end();

// The private key without line breaks
const privateKey = `-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC5Q+0Je6sZ6BuX
cTsN7pEzAaj4819UE7gM+Tf7U5AKHSKk3hN5UILtp5EuEO7h7H+lyknn/5txltA4
-----END PRIVATE KEY-----`;

const signatureBase64 = signatureFunction.sign(privateKey, 'base64');
const signatureBase64Url = replaceSpecialChars(signatureBase64);

console.log("Your JWT is: " + base64UrlHeader + "." + base64UrlPayload + "." + signatureBase64Url);

I have this code in a Zapier code-step prior to calling the custom integration and pass in the generated token object to authenticate the call.

Hopefully, this helps someone!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Michael Case
Solution 2