Hello developer community,
I’m in the process of updating my Atlassian Connect Express (ACE) app to comply with the latest security requirements, specifically by enabling signed-install
.
I have implemented a new /installed
lifecycle endpoint to handle the RS256-signed JWT from Atlassian. The installation process itself seems to complete successfully (my app returns a 204 No Content
response to Jira).
However, after the installation, any subsequent request from Jira to my app (for example, when loading a dashboard gadget) fails with a 401 Unauthorized
error. My server log shows the following error:
Unauthorized: Unable to decode JWT token: Error: Signature verification failed for input: <here is a token> with method sha256
This leads me to believe that while my custom /installed
endpoint is correctly validating the installation token, there might be an issue with how the sharedSecret
is being persisted, which then causes the standard ACE middleware (addon.authenticate()
) to fail during subsequent JWT validation.
Here are the relevant sections from my atlassian-connect.json
descriptor:
{ "authentication": { "type": "jwt" }, "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled" }, "apiMigrations": { "gdpr": true, "context-qsh": true, "signed-install": true } }
And here is a simplified version of my /installed
route handler:
app.post(‘/installed’, async (req, res) => {
try {
// 1. I extract the RS256 JWT from the request
const token = extractJwt(req);
// 2. I correctly verify it against Atlassian's public key
const claims = await verifyLifecycleJwtRS256(token, { ... });
const clientKey = claims.iss || req.body.clientKey;
// 3. I use the ACE settings manager to store the installation payload
await addon.settings.set('clientInfo', req.body, clientKey);
// 4. I return a success status
return res.sendStatus(204);
} catch (e) {
console.error('Installation failed:', e);
return res.status(401).send('Unauthorized');
}});
I use “atlassian-connect-express”: “^3.4.0”
My question is: Is this the correct approach for handling the signed-install
process? Is using addon.settings.set()
the right way to ensure that the standard ACE authentication middleware can find the sharedSecret
for later requests?
Any guidance or insight would be greatly appreciated. Thank you!