Action Required - Atlassian Connect installation lifecycle security improvements

Hi @danielwester

When the publicKey gets rotated it will always be linked to a new unique kid which makes it unlikely to return a different key. If the addon server had already received a JWT token with a kid that means the public key is already available in However, the worst case scenario will be this service becoming unavailable right after Jira/Confluence has successfully signed the token and sent the request to the lifecycle event.

what is the customer experience when we reject an installation because the key we have is not correct/available etc?

In this case, the app server will respond with 4xx status code and customer gets and error saying
The app host returned HTTP response code 4xx when we tried to contact it during installation. Please try again later or contact the app vendor.
Previously, this issue was being reported when shared secret gotten out of sync and needed to manually resolve the issue. But if CDN somehow fails to respond in future, customer can retry without having to contact us or the app Vendors.

is there actual monitoring (not integration testing) which is driving installation and uninstallation of apps outside of the Atlassian AWS infrastructure

We have a service running series of app install/uninstall scenarios almost every minute on production and our team will investigate every time we notice any failures.

Hope this answers your questions.


Hi @OfirNir, Thanks for reporting this issue.
We have patched a new version to fix this os dependent problem. Could you please check with ACSB v2.2.1 ? It should be available from maven central within a couple of hours


If every key rotation generates a new kid, it is actually ok to cache the key, right?
Just want to clarify as it is the opposite what was initially posted.


Looks good now, thanks!

1 Like

Do these changes affect Bamboo Plugins? Correct, you can cache them by kid.

No, this change does not apply to Bamboo or Bitbucket plugins


Hi @HanjooSong

The comments in this thread indicate that addon.authenticateInstall() can be used for the install and uninstall routes. The latest docs imply, however, that all lifecycle methods are similarly authenticated: “Lifecycle request contains a JWT token signed with private key […]”

Empirical evidence (and the top of this post) suggest that only the install and uninstall hooks are covered, and not others (ie. enabled/disabled). Can you confirm, and if so, update the docs?


1 Like

Can you elaborate on this please? When we add "signed-install": "enabled" to config.json, the /installed-route behaviour seems unchanged. Adding "apiMigrations": { "signed-install": true } to the descriptor seems to take the new authentication into use, and is causing not problems for us.

1 Like

Our app serves both Jira and Confluence so we have two addons. We have two different hosts (mapping to the same server) to be able to serve the correct descriptor. (e.g. and

Creating an ACE addon registers a handler for /installed POST requests and this handler also authenticates the request. The problem is, that the new authentication method requires addon.config.localBaseUrl() to match the request host. In our setup this can cause a situation where the user is installing our app to Confluence, but the Jira addon also tries to verify the installation, causing an error 'Authentication verification error (401): JWT claim did not contain the correct audience (iss) claim'

Any suggestions on how to solve this?


Do you think we can get the Connect Inspector tool updated to use the new signed install feature? This would help a lot with testing.



App upgrade hooks from hosts also appear to have asymmetric RS256 signatures, however the above docs don’t make this clear (unless I’m reading it wrong).

1 Like

@Bentley pinging you because this is the type of stale topic that should not be stale. It’s noteworthy that the answers to simple questions did continue a bit longer but the more “difficult” questions from myself and John Mort have been actively ignored



I’m using ACE with Next.js.

I’m not doing anything custom with the /install /uninstall routes but delegate to ACE with next().

server.get('*', (req, res, next) => {
    if (
      req.url === '/atlassian-connect.json' ||
      req.url === '/installed' ||
      req.url === '/uninstalled' ||
      req.url === '/enabled'
    ) {
      return next();
    app.render(req, res, req.url, { ...req.payload });

I’ve tested updating ACE to v7.3.0 and adding "signed-install": "enable" to my config and it seems to work locally.

Will this continue to work? Do I need to do anything special to the routes managed by ACE as I’m not doing anything custom (ie add addon.authenticate() ect)?


EDIT: I just realised /installed, /uninstalled and /enabled are all POSTs not GETs and aren’t being referred to anyway :flushed:

Hi @ekaukonen .
We are running a custom implementation of Atlassian Connect framework based on Play 2.

We compared Spring Boot implementation of asymmetric JWT and example implementation from atlassian docs (part “Verifying a asymmetric JWT token for install callbacks”) .

Spring Boot implementation checks both JWT signature and all the standard and non standard claims including Audience claim (com.atlassian.connect.spring.internal.jwt.AbstractJwtReader#readAndVerify ). But as per code snippets from the atlassian docs (part “Verifying a asymmetric JWT token for install callbacks”) it is recommended to check the JWT signature and the Audience claim (there is no verification of standard and non-standard claims in the example code snippets). Seems like we need to verify all the claims of an asymmetric JWT the same way as it is implemented in ACSB.

Could you please clarify, do we need to perform all claims (not only aud but all the standard claims and qsh ) checks during an asymmetric JWT token verification and why claims checks skipped in code snippets from atlassian docs?

Kind regards,

1 Like

I’m also confused by this currently. It would seem that at least the /enabled hook still uses the old JWT structure even with the "signed-install": true

  "typ": "JWT",
  "alg": "HS256"
  "sub": "557...",
  "qsh": "0e9...",
  "iss": "cca...",
  "context": {},
  "exp": 1625586123,
  "iat": 1625585223

Hi @scott.dudley. Yes, only install and uninstall hooks will be affected by this change. I will have the docs updated soon.

Yes, upgrades will call an installed lifecycle event. I will update the page to be more clear. Thanks.

Hi @aorlov. Yes, you need to verify all claims including audience. I do agree with your confusion around the examples from the docs, it emphasises that audience needs to be checked but not much about other required claims. I will update the docs to make this more clear. Thanks!


Hi @ErkkiLepre. I’d be happy to take a look at this issue if you can create a DEVHELP ticket and provide more details like your config.json and the descriptor configurations.

And also for your question related to the audience claims, I will keep you updated after adding support for list of aud claims.