Jwt token generation with RS256 and decode with utf-8 in k6

I am trying to generate jwt token with a key but it in not same as I wanted. I referred Simple JWT example for k6 load testing tool · GitHub this and used but not as generated.
In python I have used like this

jwt.encode(
    {"userNumber": 1, "username": "abc@qqq..com"}, private_key, algorithm="RS256"
).decode("utf-8")

How can we generate the token in K6?

Hi @ge_orgejoseph, welcome to the community forum :tada:

Can you please tell me what you tried with the given example, what was the result and what did you expect (what you get in python). You can generate a private key just for that so that the examples are full without compromising your real keys.

Hi @mstoykov thanks for your comments. I was able to convert payload and header to base64 and combined using “.”
And it is now matching with first two parts base64(header) + "." + base64(payload)

let secret = "anything"
let token = base64(header) + "." + base64(payload); // upto here is fine
let hash = crypto.hmac('sha256', secret, token, "base64rawurl");
console.log(token + "." + hash)  // hash value is wrong when compared with python

Signature was unable to verify. Can anyone help

I did some more digging and RS256 is using RSA which is asymetrical unlike the HS256 algorithm which is symmetrical and uses HMAC.

Unfortunately, k6 does not have API for signing with RSA and at least crypto-js and tweet-nacl do not support RSA as well. js-crypto-rsa supposedly should work, but unfortunately I can’t make it load, because of nodejs module resolution problems. And I am pretty sure that even if I managed it will probably still will have problems :(.

I then found this which I kind of made to work, but unfortunately I ran into the problem that this is both very slow and me not understanding what needs to happen(not really into cryptography at all) - for example I am now pretty sure that this code does not actually implement RSASSA-PKCS1-V1_5 signing which is what is needed for the jwt key.

I did go through some crypto extensions(and an old PR) until I :man_facepalming: and remembered that there actually is a xk6-jwt extension, unfortunately (of course) it seems to also not work :(.

Again unfortunately, I, spend way too much time on this to continue down this rabbit whole, but you can probably fix xk6-jwt - the predominant problem is (AFAIK) that it doesn’t take ArrayBuffer but instead []byte and apart from that it probably needs better interface :man_shrugging: .

Hopefully one day Extend/rewrite `k6/crypto` · Issue #2248 · grafana/k6 · GitHub will be prioritized highly enough and we will start adding APIs to k6 at that point, but until then I would recommend:

  • either use HS256 (don’t know what the implications of that are) at least for load testing
  • write your own extension (fix the linked above) and use that.

I doubt this helps you, but hopefully in the future someone will skip some of the things I’ve tried :crossed_fingers: and find a different solution :man_shrugging:

Thanking @mstoykov your research on this. I too have reached the same thoughts and idea with in day that makes me to dig a little more in K6. And your response makes the confirmation on those. I will got for creating extension that sounds good for my case.

The above post will helps and in future it help others.

1 Like

Glad it helps you @ge_orgejoseph :)!

You can also make your extension public and we will add it to the list of extensions :slight_smile:

Hi @ge_orgejoseph,

I’m tackling the same issue now - have you managed to create a working extension?

Any updates on this topic?
(We plan to start using k6, but we use RS256 for the token signature)

Hi @eugeneg,

k6 does now have support for parts of the WebCrypto API, but not the RSA ciphers specifically.

From somewhat qucik googling I managed to get this example working

import KJUR from "https://unpkg.com/jsrsasign@10.8.6/lib/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);

export default () => {
	var sJWT = KJUR.jws.JWS.sign(header.alg, sHeader, sPayload, privateKey);
	console.log(JSON.stringify(sJWT));
};

Which returns something looking okay. And is based on this stackoverflow answer.

Your mileage might vary depending on what exactly you need to work with though and do.

Hope this helps you and anyone else who comes across this.

2 Likes

Hey @mstoykov

Thanks for sharing the solution. However, I tried to run the function in k6, but got this error:

ERRO[0000] SyntaxError: https://unpkg.com/jsrsasign@10.8.6/lib/jsrsasign.js: Unexpected token (2:0)
  1 | 
> 2 | <!DOCTYPE html>
    | ^
  3 | <html lang="en">
  4 | <head>
  5 |     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
	at <internal/k6/compiler/lib/babel.min.js>:2:28536(99)
	at <internal/k6/compiler/lib/babel.min.js>:14:24413(11)
	at bound  (native)
	at s (<internal/k6/compiler/lib/babel.min.js>:1:1327(8))  hint="script exception"

Not sure if I missed something? Thanks!

Hi @ericinau

I was testing Mihail’s example and that works for me with version 0.48.0.

Can you share your “sanitized” version of the script, or are you running the exact same one and seeing this error?

If you are running the same one, can you share the k6 version you are running, and how you are running the script (the command and operating system)?

Thanks!

Thanks @eyeveebee for the reply!

Yes I was running the exact same one as above and think the error was raised by the import line:

import KJUR from "https://unpkg.com/jsrsasign@10.8.6/lib/jsrsasign.js";

My version is: v0.45.0 ((devel), go1.20.5, darwin/arm64)

Hi @ericinau

I did try with your version and it seems to work as well.

docker run --rm -i grafana/k6:0.45.0 run - <script.js
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: -
     output: -

  scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
           * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)

time="2024-01-08T18:14:43Z" level=info msg="\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbmFtZSI6Im5hbWUiLCJsbmFtZSI6Im5hbWUiLCJlbWFpbCI6ImVtYWlsQGRvbWFpbi5jb20iLCJwYXNzd29yZCI6ImFiYzEyMyQiLCJpYXQiOjE3MDQ3Mzc5NDB9.Tt_gR2w7UdgEvs_2dsP0-3UmxuifiBBQKBXdmHxbZJu0GpYCkEdTZGwWhh3eOHtnwEr5gcUCaYP0tRRN8I-VWw\"" source=console

     data_received........: 0 B 0 B/s
     data_sent............: 0 B 0 B/s
     iteration_duration...: avg=139.96ms min=139.96ms med=139.96ms max=139.96ms p(90)=139.96ms p(95)=139.96ms
     iterations...........: 1   7.061185/s


running (00m00.1s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [ 100% ] 1 VUs  00m00.1s/10m0s  1/1 iters, 1 per VU

Can you make sure you can get to https://unpkg.com/jsrsasign@10.8.6/lib/jsrsasign.js? You might have a proxy or something that is preventing direct access to that URL from where you are running the k6 script. E.g. run curl https://unpkg.com/jsrsasign@10.8.6/lib/jsrsasign.js to see if you get the js file.

I hope this helps.

Thanks @eyeveebee for the check! Looks like it is my local proxy issue.

1 Like