Forum Academy Marketplace Showcase Pricing Features

Trying to write a server-side decrypting script

Hello !

So, I’m trying to use Toolbox’s “Server Script” action to decrypt an 128-AES-CBC encrypted token.

Why server side? Because it seems to me that running a decrypting script on the client side would expose the private key, but I may be wring on this.

The script needs to accept:

  • An encrypted token (probably encoded in base64)
  • An Initiation Vector
  • A private key

And it should return the decrypted output in plain text, or an error if the decryption didn’t work out.

I came up with this node.js script:

const crypto = require(‘crypto’);

function decryptToken(token, iv, secretKey) {
try {
const decipher = crypto.createDecipheriv(‘aes-128-cbc’, secretKey, iv);
let decrypted = decipher.update(token, ‘base64’, ‘utf8’);
decrypted +=‘utf8’);
return decrypted;
} catch (err) {
return null;

const token = ‘your_encrypted_token’;
const iv = Buffer.from(‘your_iv’);
const secretKey = Buffer.from(‘your_secret_key’);

const decryptedToken = decryptToken(token, iv, secretKey);
if (decryptedToken) {
} else {
console.error(‘Decryption failed’);

I created the following Server Script action on a test page:

With data’s “Arbitrary text” being MKomzoTUb7qvh9BWioEv5g==
And Thing 1’s “Arbitrary text” being 1234123412341234

The test private key is directly in the code: sandbox-xyzx-key

In the workflow, a subsequent action saves the return of this Server Script action.

And… it doesn’t work :slight_smile:

There are no js error code while running it, but the return is empty.

I may have tried to many new things at once:

  • server script
  • data manipulating js in Bubble
  • crypto node.js

I someone could gave me a hint on how to solve that one, that’d be great! :hugs:

Why not putting the code in a plugin directly?

Some advice for debugging the problem:

  • run the code on your machine using node v14 so that you have better informations on what is generating errors
  • return the value you need but also a boolean to tell if there was an error, and also a text to tell the error message. At the beginning you can wrap everything in a try/catch and instead of returning null you return true for the boolean and the error message.

Thanks for your reply!

Trying to put the code in a plugin would be another new thing for me thrown at the issue. But do you think it would be easier?

I’m very new to js script, so I’m not sure how to apply your other advices :sweat_smile:

But thanks again. I’ll take a look at plugin creation.

I did use crypto in a server side action in a private plugin successfully so I know it’s doable.

To test locally with node:

  • download node v14 from here Index of /download/release/v14.21.2/ for your operative system and install it (eg. if you are on windows it’s probably node-v14.21.2-x64.msi)
  • download and install vscode from here (any editor will be ok, vscode it’s easy because it comes with an integrated terminal)
  • create a new folder, open vscode and open the folder with “file > open folder”
  • create a new file with “file > new text file”, then save as index.js in your folder
  • write the code in the file and save
  • open a new terminal with “terminal > new terminal”
  • run the script: write node index.js in the terminal and press enter
1 Like

Thanks for your help with node and vscode. I did manage to make it work with a plugin! Happy to have developed my first one :slight_smile:

That’s great! Congratulations! :partying_face:
I’happy I helped you with this :slight_smile:

1 Like

Hello @patrice.bonfy ,

I have exactly the same problem as you at the moment!

So it’s impossible to make a node js script via the toolbox plugin?

Would you accept to share the code of your plugin ? or your plugin directly ? :sweat_smile:
It could help me a lot !

Thanks :blush:

That’s right. Only client side libraries can be used.

1 Like

Hi @Dimo. I can try to write a quick tutorial on how I built this plugin. Are you still interested?

1 Like

Hi @patrice.bonfy,

Thank you for your answer.

Yes I am interested, thank you for taking the time for this :grinning:

Ok. It will be a very simple tutorial. Don’t hesitate to ask me further questions if you get stuck somewhere (I may or may not be able to find a solution).

  1. Create a plugin. In General settings I just gave my plugin a name, a logo and a description so that I could easily recognize it in my plugin tab.

  2. In Shared technical settings, Plugin API calls and Elements : left those empty.

  3. We only have things to do in the Actions menu and in the Settings menu

  4. In Actions: add a new action. In my usecase, I needed to decrypt aes-128-cbc, so I named the action “decrypt aes-128-cbc”

  5. Make sure that your plugin has the right node modules at the bottom of the Actions menu. In my case I needed “crypto”. When you have entered the code below, you have a button to update your package. If everything goes right, you get the green message below.

  6. Now you have to specify the “fields” of your action. This is what you will have to define when using your action in a workflow. In my case, I need the chain of characters to decrypt, the Integration Vector and the key.

  7. Then you specify the result of the action that you want to be able to use in a following action of your workflow (“result of step x”). For me it is “decrypt”

  8. And now the toughest part: actual javascript (tough for me as I did not know much about javascript. I had ChatGPT write some javascript to me, and I then adaptated it for Bubble plugin context. Here is my code:

    function(properties, context) {

    const crypto = require(‘crypto’);

    function decryptToken(token, iv, secretKey) {
    try {
    const decipher = crypto.createDecipheriv(‘aes-128-cbc’, secretKey, iv);
    let decrypted = decipher.update(token, ‘base64’, ‘utf8’);
    decrypted +=‘utf8’);
    return decrypted;
    } catch (err) {
    return ‘@error’;

    const token = properties.token;
    const iv = Buffer.from(properties.iv);
    const secretKey = Buffer.from(properties.key);

    const decryptedToken = decryptToken(token, iv, secretKey);
    return {decrypt: decryptedToken};


It’s javascript but when you call properties.something, you call the fields of you action. And you have to return a variable that has the name of your expected result (I guess. It works for me that way anyway).

  1. Now you need to be able to test it. Si go to the Settings menu and authorize your app.

    And submit your code.

  2. Go to the plugin tab of your app and install your private plugin. Test it in a workflow and iterate on your javascript code until it works :slight_smile:
    :warning: Be sure to resubmit any modification AND head to your plugins tab to switch to the latest version (I spent some time not actually testing any of my variations as I forgot to switch to the latest version of my plugin…)

I hope this will help you!

If you create a new topic for this guide it can be easier to discover it for other people in the forum.

Some tips:

  • use three backticks (```) on a new line to insert multiline code, so that is easier to read.
    For example:
const hello = "world";
  • crypto is built-in in Node. The old library is deprecated and it resolves to an empty package. There is no need to add it as a dependency.
  • check your quotes, string literals in js can be specified only with single/double quotes and backticks
  • secret keys should not be hard coded in the plugin, you should use a private key in the shared tab of the plugin editor

Thank you so much for your advice. I’ll try to take the time to republish it with better formatting and cleaner code :slight_smile:

Just a question: if you need to use different private key in different contexts of your app, you can’t use the shared tab as suggested. Is there a security problem with passing it as a field in the action ran in a backend workflow?

Thanks again!

My understanding is that private keys in the shared tab stay in the backend and are never transmitted to the frontend. In fact you can access them only in server side actions.
Any property of an action is dynamically set from your app logic and it is exposed in the context where you are using (frontend or backend).
Based on this you can decide what is the best way to handle the keys in your particular case.