Jira Connect App - problem with authorization 401

Hello,

I created an AWS Lambda function. I have successfully installed it in Jira via manifest. According to the documentation, I implemented JWT token creation. I use this token to communicate one-way with Jira. I don’t have any webhooks. My goal is to make an entry in the Issue worklog. However, when I make a request to, for example, /rest/api/3/myself, I still get a 401 status. I’ve been fighting this for three days. Can anyone advise me or verify if I have the correct implementation of creating a token and calling the endpoint?

My JWT token generation (Python):

def generate_jwt_token(self, url, method, query_params=None):        
      # # Nastav údaje pre token
      issued_at = int(time.time())
      expires_at = issued_at + 120  # 2 minutes

      payload = {
          "iss": manifest["key"], #com.solvedio.<myapp>
          "iat": issued_at,
          "exp": expires_at,
          "qsh": self.generate_query_hash(url, method, query_params),
          "aud": self.secret.base_url #https://<mytenant>.atlassian.net

      }                    

      # Sign it with sharedSecret        
      return jwt.encode(payload, self.secret.shared_secret, algorithm="HS256")      
      
 def generate_query_hash(self, url, method, query_params=None):
        
        parsed_url = urlparse(url)
        path = parsed_url.path
        canonical_url = f"{method.upper()}&{path}"

        if query_params:
            sorted_query = "&".join(f"{k}={v}" for k, v in sorted(query_params.items()))
            canonical_url += f"&{sorted_query}"
        
       canonical_url += "&"

        return hashlib.sha256(canonical_url.encode('utf-8')).hexdigest()    

My calling of endpoint:

 def check_me(self):        
        url = f"{self.security.secret.base_url}/rest/api/3/myself"  
        token = self.security.generate_jwt_token(url, "GET")

        headers = {
                "Authorization": f"JWT {token}",            
                "Accept": "application/json",   
                "X-Atlassian-Token": "no-check"             
            }        

        response = requests.get(url, headers=headers)     

        if response.status_code == 200 and response.json():
            return {
                'statusCode': 200,
                'body': response.json() 
            }
        else:
            return {
                'statusCode': response.status_code,
                'body': json.dumps({                    
                    "headers": headers,
                    "url": url,
                    "message" : response.text,
                }
                    )
            }

My installation manifest:

manifest = {
    "name": "My connector",
    "description": "Atlassian Connect app (bridge between Me and Jira)",
    "key": "com.solvedio.myconnector",
    "baseUrl": "<Url of Lambda function>",
    "vendor": {
        "name": "Solvedio",
        "url": "https://solvedio.com/"
    },
    "authentication": {
        "type": "jwt"
    },
    "lifecycle": {
        "installed": "/installed",
        "uninstalled": "/uninstalled"
    },
    "scopes": ["read", "write"],
    "apiVersion": 3,
    "modules": {
    }    
}

Thank you.

Hi @TomasBako ,

I suspect the problem might be an incorrect computation of the query string hash. This seems like a new app so I would recommend using Forge instead of Connect. With Forge, you don’t need to manage any credentials nor create tokens when calling the Jira APIs because the platform takes care of this for you.

Regards,
Dugald

In particular this part looks wrong:

  • a: pretty sure there should always be an ‘&’ after the path and before the query
  • b: it will also fail if you ever have any query parameters with list values in the form ?id=1&id=2&id=3 - since it needs to canonicalise fo &id=1,2,3
1 Like

Thank you very much (or god bless you :slight_smile: ). This missing ampersand on the end of query causes the 401.

On the other way, this query produces 401 anyway:

        search_url = f"{self.security.secret.base_url}/rest/api/3/user/search"  
      
        query_params={
            "query" : "tomas.bako"
        }      

        token = self.security.generate_jwt_token(search_url, "GET", query_params)

        headers = {
            "Authorization": f"JWT {token}",            
            "Accept": "application/json",                        
        }        

        response = requests.get(search_url, headers=headers, params = query_params)  

Endpoint /myself works fine, but only this one, the others fails on 401. I have no idea why.
Thanks for advice.

Thank you for your answer.

Unfortunately a Forge application is not suitable for our one-way scenario:

customer system → AWS Lambda → Jira API

We don’t need any frontend or requests from Jira back to Lambda.

Pretty sure your query params → qsh is still incorrect if you haven’t changed it other than adding the ‘&’

I suggest to log the url including query and the canonicalised version of it, will make the errors pretty clear.

I have these in logs:

Url:

https://bittersweat.atlassian.net/rest/api/3/user/search

Canonical url:

GET&/rest/api/3/user/search&query=tomas.bako&

There should not be an ‘&’ after the query.

1 Like