Lifecycle installed callback not firing--need sharedSecret

Hi,

Use atlassian-connect-express, i have a route defined in routes/index.js as

app.post('/path/installed', function(req,res) {
    console.log([req,res]);
    gsecret = req.body.sharedSecret;
  });

This doesn’t appear to be firing at all (no console output, no value for gsecret), even though the URI is called (appears in log) as expected and returns a 204. Obviously my goal is to store the sharedSecret value so I can use it later. How do I either get this callback to work or get the sharedSecret some other way?

Any insight you can provide will be most helpful.

Thanks,
Dave

I believe Installed route is handled by ACE automatically and it’s fired inside ACE, so that’s probably why you don’t see your route being fired. Secrects are handled by ACE too, they are stored in database that you provided in config file inside AddonSettings table, so i believe you won’t even need to access them inside any other routes, but you might want to access clientKey from which request comes from, it can be accessed inside routes from req.context.clientKey

1 Like

Hi–thanks for your reply.

Unfortunately this approach does not ostensibly provide access to the sharedSecret which I need in some route callbacks to create jwt tokens (after all, isn’t that the point?)

The clientKey doesn’t change between installs and can be a constant. The sharedSecret can and does, so I need to capture it somehow.

I can see it, and access it in the database via cli, but how do I get it out of the database? Do I actually need to query the db? The lack of sample code and docs betrays that notion. Isn’t it stored in the context anywhere? It even gets printed in the log by the installed callback. All the documentation about jwt refers to the sharedSecret and implies it can be captured during installed callback but there is no example code nor any indication of how this is done.

To clarify, my cloud addon includes a jiraSearchRequestViews module which does not render in an iframe. I have to pass jwt tokens in subsequent requests. These tokens need to be created using the sharedSecret. It works when I paste the current sharedSecret string value into the code.

...
      let clientKey = '...';  // <- doesn't change, can be set as constant
      let now = moment().utc();
      let secret = '...'; // <- often changes, how to derive?

      let tokenReq = {
        method:'POST',
        originalUrl: '/orig-rsrc'
      };
      let token = {
        "iss":clientKey,
        "iat":now.unix(),
        "exp":now.add(10,'minutes').unix(),
        "qsh":jwt.createQueryStringHash(tokenReq)
      }
      res.render('path/rsrc', {
        "token":jwt.encode(token,secret), ...
      }
...

What’s a fella to do?

As always, any insight you can provide will be most helpful.

Thanks!
Dave

You should definitely check ACE docs if you didn’t. If your goal is to provide JWT token to your rendered page in order to authenticate later requests from it to your add-on, then it’s already done by ACE with special context variables that are injected into render context. The one you need is called token, you can read in docs.
So, if you need JWT token to authenticate request from page you rendered, just include token variable inside hbs template, then on client side extract it and use it when requesting back to your add-on. No need to pass token to render function inside route, it’s should be already injected. Also you need to add to your routes which will be requested with ACE middleware for routes addon.checkValidToken. This will handle token validity.
But if you want to create your own JWT instead of the provided by ACE, then yes, you might need a secret, but then, you can make secret for your server, instead of using clients secrets.

Aha!

Indeed you are correct.

From https://bitbucket.org/atlassian/atlassian-connect-express/overview (the whole relevant section for context, but in particular, the last line below:)

Special context variables

atlassian-connect-express injects a handful of useful context variables into your render context. You can access any of these within your templates:

  • title: the add-on’s name (derived from atlassian-connect.json)
  • addonKey: the add-on key defined in atlassian-connect.json
  • localBaseUrl: the base URI of the add-on
  • hostBaseUrl: the base URI of the target application (includes the context path if available)
  • hostStylesheetUrl: the URL to the base CSS file for Connect add-ons. This stylesheet is a bare minimum set of styles to help you get started. It’s not a full AUI stylesheet.
  • hostScriptUrl: the URL to the Connect JS client. This JS file contains the code that will establish the seamless iframe bridge between the add-on and its parent. It also contains a handful of methods and objects for accessing data through the parent (look for the AP JS object).
  • token: the token that can be used to authenticate calls from the iframe back to the add-on service

Note that in description of token there is an explicit reference to iframe. This was where I went off course because I followed a path as such:

  1. Create web-panel addon which uses an issue update event listener and AP lib.
  2. Attempt to extend this functionality to bulk-edit–no dice
  3. Pursue jiraSearchRequestViews solution instead – no iframe here, so no implicit access to AP, need to hand-roll
  4. Wrap ajax requests to REST API in ace routes
  5. Get 401 errors–oh yeah, i need jwt. how do I get a token?
  6. Consult https://developer.atlassian.com/cloud/jira/platform/authentication-for-apps/ and https://developer.atlassian.com/cloud/jira/platform/understanding-jwt/
  7. Scour this forum (and the other one,) and eventually post a question.

So unfortunately the “authenication-for-apps” document has no explicit reference to the {{token}} variable in the render context, and although I went back to the overview more than once, I ignored the bit about token because no iframe.

Thanks again for taking an extra moment to understand my confusion and help me resolve it.

Dave

Ye, i feel you :slight_smile: Though, two links that you provided to jira cloud docs are about whole atlassian connect infrastructure enabling developers for using whatever backend languages they like. And ACE is an official implementation of those things (i mean connecting both jira and plugin). But if you want something other then node.js or java, for example dart :stuck_out_tongue: you have to implement all those things by yourself, managing installed lifecycle, storing client info, checking their validity etc just as described in those links.

There is no denial that the documentation is a bit confusing. As I am trying to develop a confluence connect app to make jira cloud rest api request:

  • ok, we have to get an authentication token from a request to “https://auth.atlassian.io/oauth2/token”.
    Most of the data is available in the ACE context, but sharedSecret isn’t.
  • in the documentation of 3LO, it was mentioned that the sharedSecret is available with an addon installed callback function but cannot find any examples in bitbucket.
  • using the already generated JWT token (2LO) results in validation errors from “https://auth.atlassian.io/oauth2/token” about wrong expiry time, missing aud, etc.

So far, I have defined a custom JWS token but missing only sharedSecret.
The only error I get from “https://auth.atlassian.io/oauth2/token” is 400 invalid_grant, Invalid JWT signature.

Some suggest catch the event (defined in lifecycle at atlassian-connect.json) by using:
(where does this belong?)

addon.on('installed', function(clientKey, clientInfo, req){
    ....
});

or in routes/index.js:

app.post('/installed', addon.authenticate(), function(req, res) {
    ...
});

but that results in 401.

or:

app.post('/installed', function(req, res) {
    ...
});

Does contain sharedSecret, but causes “connect.install.error.remote.host.timeout” at ngrok.io, app installation fails.

However this works:

app.post('/installed', function(req, res) {
    ...
    res.send(200);
});

Turns out you need to add the callback fuction in app.js…:

app.post('/installed', (req, res, next) => {
    process.env.my_app_share_secret = req.body.sharedSecret;
    
    next();
});

Then you can use proces.env.my_app_share_secret variable in index.js.

I got this from an example at:
https://bitbucket.org/atlassian/bb-cloud-jwt-grant-sample-app/src/master/