How to authenticate Nodejs connect app for JIRA Cloud?


I have been trying to create a JWT token using atlassian-jwt library and pass that as “Authorization” header to make rest api calls in my nodejs app but keep getting error message

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  <message>Client must be authenticated to access this resource.</message>

I am using ClientKey for iss and SharedSecret for secret to create JWT token…Is this correct?
When I install Addon, the response I get back has the following

pid: xxxx,
clientkey: xxxxx,
public key: xxxxxxxx,
sharedSecret: xxxxxxxx,
and some other fields

Can someone guide me with respect to creating of valid JWT token and making REST API calls from a nodejs app?

Assuming that you are referring to this atlassian-jwt library, can you share the code snippet when you created the token? Also, please share how you called the REST API.

As an aside, I use atlassian-connect-express when working with Node.js - it was fairly straight forward when I used it before. Might be worth checking if it can help you out.


Thanks iragudo
I would like to keep the code simple to begin with…using connect express library means I am adding code which I don’t understand at the moment and half of it I might not need it as well. So I am trying to generate a simple JWT token and trying to update a Jira issue.

Below is the sample code from my small app

  const req = jwt.fromMethodAndUrl('PUT', '/rest/api/3/issue/XX-21?overrideScreenSecurity=true&overrideEditableFlag=true');

    const tokenData = {
        "iss": 'addon-key',
        "iat": now.unix(),                    // The time the token is generated
        "exp": now.add(3, 'minutes').unix(),  // Token expiry time (recommend 3 minutes after issuing)
        "qsh": jwt.createQueryStringHash(req) // [Query String Hash](

    const token = jwt.encode(tokenData, secret);
  axios.defaults.headers.common['Authorization'] = `JWT ${token}`;
  let options = {
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    body: `{
      "fields": {
        "xy-coordinates": {
          "x": "${location.xValue}",
          "y": "${location.yValue}"

  axios.put('${key}?overrideScreenSecurity=true&overrideEditableFlag=true', options)
  .then(res => {
  .catch(err => console.log(err))

Can you let me know what am I missing in this request as the error message I get back is

<h1>Unauthorized (401)</h1>


Where from do you get secret in your code?

It is essential part, if you look at atlassian-connect-express, you may see that secret is obtained upon registering the app in Jira (aka handshake)

Thank you.

Yes, I retrieve it from the Installed callback response when app is registered in JIRA. I save that sharedSecret and use it to generate the token.

@iragudo any help would be appreciated

Hmm… I do not see any glaring issues in the code you’ve placed. To isolate the issue, can you try a REST API that does not have some sort of variable (issue ID) in it? For example, try Get current user REST API.

If that works, it probably means that the JWT generation is correct but there might be some problem when you call jwt.fromMethodAndUrl (maybe the parameters?). If it still does not work, maybe there’s something going on with atlassian-jwt.

I haven’t used the library in a while, I might’ve missed something.

Thanks @iragudo
I tried the GET call as you suggested and I got the following error

Basic auth with password is not allowed on this instance

I went through the steps again and saw that I have enabled the “Development” mode for my JIRA instance. Is there something I am missing or for some reason this instance is not created properly?

This is interesting, you got this basic auth related message even though you used JWT authentication?

I am not sure, however, if you suspect that it might be instance specific, you can try spinning up a new dev instance and try it out.

Hello @aagrawal2 @dmorrow, maybe you have an idea on what we’re missing. Thanks!

Hi @jmadan,

Do you see any extra details in the response? Sometimes the body of the message indicates something like a mismatching QSH claim.


I have been able to resolve this issue… 2 things

  1. From docs I understood that only relative url needs to be passed for creation of jwt token but it turns out it needs the full url with domain as well
  2. Increase the token lifespan from 3 minutes.

The custom field I had created was “Read_Only” as I only wanted to modify it through API call and not by any user…I still have not be able to modify this though…even when I pass on the querystrings overrideScreenSecurity=true and overrideEditableFlag=true but I am able to modify field of type text.

1 Like

I figured that this lifecycle callback function will contain the sharedSecret in the request:'/installed', function(req, res) {

But how do you set this sharedSecret in storage? Currently in my config.json, storage type is “memory” for development.