Using crypto.subtle to Verify JWT Signing Keys with Newman

Hello Postmanauts,

I am able to verify jwt signatures in Postman, but am not able to get it to work with Newman.

This works in the normal Postman “Scripts” section:

const CryptoJS = require('crypto-js');
pm.test('JWT Signature is Valid', function () {
    let recreatedSignature;
    try {
        // Generate the hash using HMAC-SHA256 and encode to Base64
        recreatedSignature = CryptoJS.HmacSHA256(
                `${jwtHeaderEncoded}.${jwtPayloadEncoded}`,
                jwtSigningKey
            )
            .toString(CryptoJS.enc.Base64)
            .replace(/=*$/, '');
        console.log('recreatedSignature', recreatedSignature);
    }
    catch(e) {
        throw new Error('Could not recreate signature | ' + e)
    }
    
    pm.expect(jwtSignature).to.be.a('string').that.equals(recreatedSignature);
});

However, when I run this in Newman, I get the following error:

image

When I remove require('crypto-js'), that error goes away.

I found this post here where Daniel Kimmelmann states that crypto.subtle is supported, and not require('crypto-js').

Following this post, I came up with:


pm.test('JWT Signature is Valid', function () {
    let recreatedSignature;
    try {
        const encoder = new TextEncoder();
        const data = encoder.encode(`${jwtHeaderEncoded}.${jwtPayloadEncoded}`)
        crypto.subtle.importKey('raw', new TextEncoder().encode(jwtSigningKey), {
            name: 'HMAC',
            hash: 'SHA-256',
        }, false, ['sign'])
            .then((key) => crypto.subtle.sign('HMAC', key, data))
            .then((signature) => {
                recreatedSignature = _arrayBufferToBase64(signature);
                pm.expect(jwtSignature).to.be.a('string').that.equals(recreatedSignature);
            })
            .catch((error) => {console.log(error)});
    }
    catch(e) {
        throw new Error('Could not recreate signature | ' + e)
    }
});

// https://stackoverflow.com/a/9458996/27294094
function _arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return btoa( binary );
}

There are 2 issues:

  1. The computed signature does not match
  2. The Promise flow breaks the Chai tests - it reports success when there is a failure.

Thank you for taking time to help me out!

Hey @matthew-beck-cpc :waving_hand:

Thanks for posting the question - Daniel is talking about the Postman Flows Evaluate block which is a different context to the Script sandbox of the request.

Which version of Newman is this? There were some updates to Postman that included new global objects that might not be part of Newman?

Do you get the same output when using the Postman CLI?

Hi Danny,

I am using Newman version 6.2.1 with Node.js 22.11.0 on Windows 11.

I think I have resolved the promise flow issue by using async/await, as well as passing a done callback to the test function, thanks to this StackOverflow answer here.

This is now working without require('crypto-js') - and it is properly calculating the signature, and it properly reports successes and failures to me in the Postman desktop application:

pm.test('JWT Signature is Valid', async function (done) {
    try {
        
        const encoder = new TextEncoder();
        const data = encoder.encode(`${jwtHeaderEncoded}.${jwtPayloadEncoded}`);

        let key = await crypto.subtle.importKey('raw', encoder.encode(jwtSigningKey), {
            name: 'HMAC',
            hash: 'SHA-256',
        }, false, ['sign']);

        let signature = await crypto.subtle.sign('HMAC', key, data);

        let recreatedSignature = _arrayBufferToBase64(signature).replace(/=*$/, '');
        console.log('recreatedSignature', recreatedSignature);

        pm.expect(jwtSignature).to.be.a('string').that.equals(recreatedSignature);
        
        done();
    } catch(err) {
        done(err)
    }
});

However, I am getting this error now:
image

Even though it says it is supported on the link you provided:

I was not aware of the Postman CLI before this. I just installed Postman CLI version 1.13.1. Interestingly, the Postman CLI works for all my other requests that use require('crypto-js'), but also fails with const encoder = new TextEncoder();, even though it does work in the regular Postman desktop app:

So the only remaining mystery that I can not figure out is this:

Why is new TextEncoder() not working in Postman CLI or Newman, while it does work in the Postman Windows desktop app?

Update:

I have now gotten this to work without having to call new TextEncoder() - with this str2ab function to replace the encoder.encode function used previously (thanks to this StackOverflow answer):

function str2ab(str) {
    const buffer = new Uint8Array(str.length);
    for (let i = 0; i < str.length; i++) {
      buffer[i] = str.charCodeAt(i);
    }
    return buffer;
}

However, now I am getting this error in both Postman CLI and Newman:

Again, just like TextEncoder, the crypto property is purportedly supported

I am calling it like crypto.subtle - which works in the desktop app. Is there another way to call it?

Hey @matthew-beck-cpc :waving_hand:

Thanks for the updates, those help - As those have been recently introduced to Postman, there may be a slight delay in them being available outside of the desktop or web platform.

I will need to check with the team to know for sure :folded_hands:

Thank you for your attention to this!

It seems I would be able to get the Postman CLI to work with crypto-js, but support for this does seem to be on its way out - I get this warning in the desktop app:

image

I’ll probably be writing a lot of Postman tests in the upcoming months, and by writing I really mean copy-pasting existing request scripts, so the sooner I know which way to go in this fork in the road the better :slight_smile:

I’m still waiting to hear back from the team about any updates to the Postman CLI.

@malvika-chaudhary are you able to provide any insights here :folded_hands: