17

I am trying to create a signed JWT in postman with the following code

function base64url(source) {
    // Encode in classical base64
    encodedSource = CryptoJS.enc.Base64.stringify(source);

    // Remove padding equal characters
    encodedSource = encodedSource.replace(/=+$/, '');

    // Replace characters according to base64url specifications
    encodedSource = encodedSource.replace(/\+/g, '-');
    encodedSource = encodedSource.replace(/\//g, '_');

    return encodedSource;
}

function addIAT(request) {
    var iat = Math.floor(Date.now() / 1000) + 257;
    data.iat = iat;
    return data;
}


var header = {
    "typ": "JWT",
    "alg": "HS256"
};

var data = {
    "fname": "name",
    "lname": "name",
    "email": "email@domain.com",
    "password": "abc123$"
};

data = addIAT(data);

var secret = 'myjwtsecret';

// encode header
var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
var encodedHeader = base64url(stringifiedHeader);

// encode data
var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
var encodedData = base64url(stringifiedData);

// build token
var token = encodedHeader + "." + encodedData;

// sign token
var signature = CryptoJS.HmacSHA256(token, secret);
signature = base64url(signature);
var signedToken = token + "." + signature;

postman.setEnvironmentVariable("payload", signedToken);

Code taken from https://gist.github.com/corbanb/db03150abbe899285d6a86cc480f674d .

I've been trying to input the PEM as the secret but does not work. Also can't find any HmacSHA256 overload that takes a PEM.

How can that be done?

1 Answers1

22

The mention of postman changed this. I have a solution for you, but it's not exactly a clean way by any mean.

You'll need to create a request that you will need to execute whenever you open postman. Go as follows:

Side-loading jsrsasign-js

The purpose of this request is to side-load jsrsasign-js and storing it in a global Postman variable.

Once this is done, you can then use this content elsewhere. For every request you need a RSA256 JWT signature, the following pre-request script will update a variable (here, token) with the token:

var navigator = {};
var window = {};
eval(pm.globals.get("jsrsasign-js"));

function addIAT(request) {
    var iat = Math.floor(Date.now() / 1000) + 257;
    data.iat = iat;
    return data;
}

var header = {"alg" : "RS256","typ" : "JWT"};
var data = {
    "fname": "name",
    "lname": "name",
    "email": "email@domain.com",
    "password": "abc123$"
};

data = addIAT(data);

var privateKey = "-----BEGIN RSA PRIVATE KEY----- \
MIIBOQIBAAJAcrqH0L91/j8sglOeroGyuKr1ABvTkZj0ATLBcvsA91/C7fipAsOn\
RqRPZr4Ja+MCx0Qvdc6JKXa5tSb51bNwxwIDAQABAkBPzI5LE+DuRuKeg6sLlgrJ\
h5+Bw9kUnF6btsH3R78UUANOk0gGlu9yUkYKUkT0SC9c6HDEKpSqILAUsXdx6SOB\
AiEA1FbR++FJ56CEw1BiP7l1drM9Mr1UVvUp8W71IsoZb1MCIQCKUafDLg+vPj1s\
HiEdrPZ3pvzvteXLSuniH15AKHEuPQIhAIsgB519UysMpXBDbtxJ64jGj8Z6/pOr\
NrwV80/EEz45AiBlgTLZ2w2LjuNIWnv26R0eBZ+M0jHGlD06wcZK0uLsCQIgT1kC\
uNcDTERjwEbFKJpXC8zTLSPcaEOlbiriIKMnpNw=\
-----END RSA PRIVATE KEY-----";

var sHeader = JSON.stringify(header);
var sPayload = JSON.stringify(data);

var sJWT = KJUR.jws.JWS.sign(header.alg, sHeader, sPayload, privateKey);

pm.variables.set('token', sJWT);

In order:

  • I define mock window and navigator objects as jsrsasign-js needs them.
  • I then eval() the content of what we fetched earlier in order to rehydrate everything
  • The rest of your code is simple usage of jsrsasign-js. Your token info is there, and I've defined a private key there. You can change this or use an environment variable; it's just there for demo purposes. I then simply use the rehydrated library to sign it, and set the variable to the value of the signed JWT.

A PEM, as you refer to it, is a container format specifying a combination of public and/or private key. You're using it to sign using HMAC-SHA256, which operates on a shared secret. This obviously isn't going to work (unless you take the poor man's approach and use your public key as the shared secret).

Fortunately enough, there are other signature methods defined in the RFCs. For instance, there is a way to sign using RSA, and a very convenient way of defining a public key as a JSON web key (JWK). We're going to be leveraging both.

I've generated a key pair for testing, they're named out and out.pub. Generation tool is genrsa (and as such, they're an RSA keypair).

In order to sign, we're going to have to change a few things:

  • We're changing algorithms from HS256 to RS256, as explained above
  • We're going to need a new library to do the signing itself, as crypto-js does not support asymmetric key crypto. We'll fall back to the native crypto module, though there are pure-JS alternatives

The code:

var CryptoJS = require("crypto-js");
var keyFileContent = require("fs").readFileSync("./out");
var pubkey = require("fs").readFileSync("./out.pub");
var base64url = require("base64url");
var nJwt = require("njwt");
function addIAT(request) {
    var iat = Math.floor(Date.now() / 1000) + 257;
    data.iat = iat;
    return data;
}


var header = {
    "typ": "JWT",
    "alg": "RS256"
};

var data = {
    "fname": "name",
    "lname": "name",
    "email": "email@domain.com",
    "password": "abc123$"
};

data = addIAT(data);

// encode header
var stringifiedHeader = JSON.stringify(header);
var encodedHeader = base64url(stringifiedHeader);

// encode data
var stringifiedData = JSON.stringify(data);
var encodedData = base64url(stringifiedData);

// build token
var token = encodedHeader + "." + encodedData;

// sign token
var signatureAlg = require("crypto").createSign("sha256");
signatureAlg.update(token);
var signature = signatureAlg.sign(keyFileContent);
signature = base64url(signature);
var signedToken = token + "." + signature;

console.log(signedToken);

// Verify
var verifier = new nJwt.Verifier();
verifier.setSigningAlgorithm('RS256');
verifier.setSigningKey(pubkey);
verifier.verify(signedToken, function() {
  console.log(arguments);
});

And that's it! It's quite literally that simple, although I would not recommend rewriting the sign() function from crypto from scratch. Leave it to a library that has had thorough inspection by the community, and crypto is pretty serious business.

Community
  • 1
  • 1
Sébastien Renauld
  • 19,203
  • 2
  • 46
  • 66
  • I cant use that in Postman. Postman is refusing to load base64url and njwt modules, for some reason it loads crypto-js –  Dec 30 '18 at 10:55
  • Postman's sandbox only allows some node modules https://learning.getpostman.com/docs/postman/scripts/postman_sandbox_api_reference/ (`crypto-js` is the only crypto related module) – janniks Jan 03 '19 at 19:32
  • 3
    That's going to be really fun then, as cryptojs doesn't support RSA. – Sébastien Renauld Jan 04 '19 at 02:38
  • In other words without creating a custom javascript code for running this, there is no way? –  Jan 07 '19 at 17:44
  • @Aleksandar There might be a way. I'm testing something which is slightly hackish but will work for you, I think. – Sébastien Renauld Jan 07 '19 at 18:32
  • @Aleksandar it actually works! It's clunky, but it works. Sideloading `jsrsasign-js` has never been more fun – Sébastien Renauld Jan 07 '19 at 18:45
  • I keep getting `undefined: undefined` when trying `KJUR.jws.JWS.sign("RS256", sHeader, unsignedInput, privateKey);` but I'm trying to sign a Google Service Account key so I just need to sign a section and build the JWT myself (idk if all JWT follow the base64header.base64claimset.base64signature format) – Kyle Calica-St Dec 17 '19 at 22:52
  • @KyleCalica-St You did follow the instructions, including the pre-load request to get `jsrsasign-js` in the state table of postman, right? As for your question itself, all `JWT`s are compose of (at least) 3 base64 section delimited by dots. – Sébastien Renauld Dec 18 '19 at 00:12
  • Yeah I did a pre-load request. https://stackoverflow.com/questions/59383521/postman-pre-request-script-to-create-jwt-for-google-service-account I can also share a link to my collection: https://www.getpostman.com/collections/5545d5302f8544223e59 – Kyle Calica-St Dec 18 '19 at 00:19
  • Seems like this is the issue: https://medium.com/@jkohne/hi-klaasjan-tukker-im-trying-to-get-the-library-import-and-eval-to-work-for-jsrsasign-c0c457ddd23f But trying to see how I might have pre-loaded jsrsasign wrong? I've added a link to my collection above. – Kyle Calica-St Dec 18 '19 at 00:23
  • looking at the minified code i can kinda recreate it with JWSJS but then i have trouble trying to create the signature which was the reason for using this library ... – Kyle Calica-St Dec 18 '19 at 00:34
  • 1
    solved it: Private Key had whitespace characters in it. They need to have the key formatted and without escaped whitespace. – Kyle Calica-St Dec 18 '19 at 18:47
  • Published my Postman Collection for anyone else who has problems and needs to see it: https://documenter.getpostman.com/view/8140651/SWECYFyf?version=latest – Kyle Calica-St Dec 19 '19 at 02:30
  • 1
    That's one of the most underrated answers here at SO, even with the bounty given. ) I ended up with slightly different approach of handling PK, making it a part of environment setup (and thus avoiding the need to cut it from pre-req script). – raina77ow Feb 09 '21 at 10:46
  • This epic answer worked for me without any changes other than to use a cdn for the library: https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/8.0.20/jsrsasign-all-min.js – amurrell Apr 06 '21 at 22:34
  • @amurrell Don't hesitate to suggest an edit :-) – Sébastien Renauld Apr 07 '21 at 23:46
  • 1
    I had to update the "setup" request wit the url https://raw.githubusercontent.com/kjur/jsrsasign/master/jsrsasign-all-min.js And it worked like a charm. Thanks a lot! – Mudo Jan 11 '23 at 15:51