This overview explains how Connect apps can perform user authorization checks against the permission model defined in Atlassian products.
Requests from Atlassian to Connect apps are authenticated using JSON Web Tokens (JWT) as described in Understanding JWT for Connect apps. These JWTs describe the user, the context (for example, a Jira issue or Confluence page), and the site the request was made from.
After authenticating a request, Connect apps must perform authorization checks to ensure only permitted users can execute actions within the app. These authorization checks are critical because:
Performing adequate authorization checks within a Connect app relies on a few concepts:
Your app should implement authorization checks:
Depending on your app and the interactions with the Atlassian cloud product APIs, your app should do one of the following:
After authenticating a request to your app, perform an explicit authorization check using the permission check APIs:
These APIs allow you to check the permissions of the current user, or another user in the case of web triggers,
without having to request privileged scopes as only the READ
scope is required for permission checks.
If you want to reduce the latency overhead of the permission checks, cache the positive result (for example, user U has access to issue Y) for up to 15 minutes. This 15-minute duration matches the expiry time on context JWTs.
We do not recommend attempting to build an "offline copy" of the product permission model in your app, as it would quickly become out of date from the permissions enforced in the products.
Below are some examples of explicitly authorizing requests using JavaScript, that are compatible with Atlassian Connect Express. These examples can be adapted to other languages or frameworks your app may use. Some frameworks, such as Atlassian Connect Express 7.1.5 or later, contain request middleware that make checking authorization easier for apps.
Use the Jira get bulk permissions API to determine the global permissions assigned to a Jira user, including custom global permissions defined by your app. This can be used to restrict an API the app exposes so only Jira admins can call it. To check anonymous user permissions, make the request anonymously (without authentication).
1 2function userIsJiraAdmin(httpClient, userAccountId) { return new Promise((resolve, reject) => httpClient.post( { url: "/rest/api/3/permissions/check", headers: { "X-Atlassian-Token": "nocheck", }, anonymous: !userAccountId, json: { globalPermissions: ["ADMINISTER"], accountId: userAccountId, }, }, function (err, _, body) { if (err) { reject(err); return; } if (body.hasOwnProperty("errors")) { reject(body.errors); return; } const isAdmin = body.globalPermissions.indexOf("ADMINISTER") !== -1; resolve(isAdmin); } ) ); } app.get("/my-app-endpoint", [addon.authenticate()], async (req, res) => { // The user ID based on the context JWT // This could be any user ID if you needed to check if a different user was an admin const userId = req.context.userAccountId; if (!(await userIsJiraAdmin(addon.httpClient(req), userId))) { res.status(401).send("Unauthorized: you must be a Jira admin"); return; } // perform admin-only actions res.status(200).send("OK"); }); // ACE 7.1.5+ example with context JWTs app.get("/my-app-context-endpoint", [addon.authenticate(true), addon.authorizeJira({ global: ["ADMINISTER"] })], (req, res) => { // perform admin-only actions res.status(200).send("OK"); });
Use the Jira get bulk permissions API to determine the permissions that a user has to specific Jira issues or projects, including custom project permissions defined by your app. For example, restricting an endpoint so only Jira project admins can access it.
1 2function userHasProjectPermission(httpClient, userAccountId, projectPermission, projectId) { return new Promise((resolve, reject) => httpClient.post( { url: "/rest/api/3/permissions/check", headers: { "X-Atlassian-Token": "nocheck", }, // in the case of no user ID, make the request anonymously (e.g. with no Authorization header) anonymous: !userAccountId, json: { projectPermissions: [{ permissions: [projectPermission], projects: [projectId], }], accountId: userAccountId, }, }, function (err, _, body) { if (err) { reject(err); return; } if (body.hasOwnProperty("errors")) { reject(body.errors); return; } const isProjectAdmin = body.projectPermissions.permission === projectPermission && body.projectPermissions[0].projects.indexOf(projectId) !== -1; resolve(isProjectAdmin); } ) ); } app.get("/my-app-endpoint/:projectId", [addon.authenticate(true /* allow context JWTs */)], async (req, res) => { // The user ID based on the context JWT // This could be any user ID if you needed to check if a different user was an admin const userAccountId = req.context.userAccountId; // The content the user is interacting with const { projectId } = req.params; const canAdmin = await userHasProjectPermission(addon.httpClient(req), userAccountId, "ADMINISTER_PROJECTS", projectId); if (!canAdmin) { res.status(401).send("Unauthorized: you don't have access to administer this project"); return; } // do something with the project, e.g. with the product APIs or your own database res.status(200).send("ok"); }); // ACE 7.1.5+ example with context JWTs app.get("/my-app-context-endpoint", [addon.authenticate(true), addon.authorizeJira({ project: ["ADMINISTER_PROJECTS"] })], (req, res) => { // do something with the project res.status(200).send("OK"); });
Use the Confluence get user with operations API to determine the operations that a user can perform. This can be used to restrict an API that the app exposes, so only Confluence admins can request it.
1 2function userIsConfluenceAdmin(httpClient, userAccountId) { return new Promise((resolve, reject) => httpClient.get( { url: `/rest/api/user?accountId=${encodeURIComponent(userAccountId)}&expand=operations`, json: true }, function (err, httpResponse, body) { if (err) { reject(err); return; } const isAdmin = body.operations.find( (op) => op.operation === "administer" && op.targetType === "application" ); resolve(isAdmin); } ) ); } app.get("/my-app-endpoint", [addon.authenticate()], async (req, res) => { // The user ID based on the context JWT // This could be any user ID if you needed to check if a different user was an admin const userId = req.context.userAccountId; if (!(await userIsConfluenceAdmin(addon.httpClient(req), userId))) { res.status(401).send("Unauthorized: you must be a Confluence admin"); return; } // perform admin-only actions res.status(200).send("OK"); }); // ACE 7.1.5+ example with context JWTs app.get("/my-app-context-endpoint", [addon.authenticate(true), addon.authorizeConfluence({ application: ["administer"] })], (req, res) => { // perform admin-only actions res.status(200).send("OK"); });
Use the Confluence check content permissions API to determine the permissions that a user has to Confluence content, such as a page or comment.
1 2function userCanReadContent(httpClient, userAccountId, contentId) { // make sure the content ID is valid to prevent traversal if (!/^[A-Z0-9-]+$/i.test(contentId)) { throw new Error("Invalid content ID"); } return new Promise((resolve, reject) => httpClient.post( { url: `/rest/api/content/${encodeURIComponent(contentId)}/permission/check`, headers: { "X-Atlassian-Token": "no-check", }, json: { subject: { type: "user", identifier: userAccountId, }, operation: "read", }, }, function (err, httpResponse, body) { if (err) { reject(err); return; } resolve(body.hasPermission); } ) ); } app.get("/my-app-endpoint/:contentId", [addon.authenticate(true /* allow context JWTs */)], async (req, res) => { // The user ID based on the context JWT // This could be any user ID if you needed to check if a different user was an admin const userAccountId = req.context.userAccountId; // The content the user is interacting with const { contentId } = req.params; const canRead = await userCanReadContent(addon.httpClient(req), userAccountId, contentId); if (!canRead) { res.status(401).send("Unauthorized: you don't have access to this content"); return; } // do something with the content, e.g. with the product APIs or your own database res.status(200).send("ok"); }); // ACE 7.1.5+ example with context JWTs app.get("/my-app-context-endpoint", [addon.authenticate(true), addon.authorizeConfluence({ content: "read" })], (req, res) => { // do something with the conten res.status(200).send("OK"); });
If your app only interacts with product APIs and not other external systems or data, you may be able to defer authorization checks to the product APIs.
If your app is static, and doesn't have a server component or your app server does not need
to interact with the Atlassian product APIs, the JavaScript AP.request
can be used to defer
authorization to Atlassian.
This works best when:
If your app already uses or relies on user impersonation, make requests to Atlassian APIs using the interacting user, rather than as the app. This ensures the app is not providing access to data that the interacting user wouldn't normally be able to access.
There are limited cases where apps need the ACT_AS_USER
scope. Its usage is strongly discouraged,
as it's highly privileged and requires consideration on how and when the app would opt to impersonate
users. Apps should not add the ACT_AS_USER
scope for authorization purposes.
This approach works best when:
ACT_AS_USER
scope; andRate this page: