← All insights

Atlassian Forge

Checking permissions in Forge: when to use asUser and asApp

A Jira issue-editing example shows where user permissions, app permissions, and business rules need separate checks.

A Forge app works perfectly when you test it as a Jira administrator. You switch to an ordinary project member, try the same action, and get a permission error. It can be tempting to change the API call from asUser() to asApp() and move on.

Before doing that, check whether the error is the correct result. If the user isn’t allowed to edit the issue, making the request succeed with the app’s permissions may introduce the very problem the platform was preventing.

Follow one operation all the way through

Take an app that lets a project manager update an issue’s reporting classification. The browser sends an issue ID and the new classification to a resolver. The resolver calls Jira.

There are two permission decisions here. Jira controls whether the caller can edit that issue. Your app may impose an additional rule: perhaps only the reporting owner should be able to change the classification. Passing the first check doesn’t satisfy the second.

Write those decisions down before choosing the API context. It makes the code review much easier: a reviewer can see why the app needs a particular permission and where that permission is checked.

Start with the user’s permissions

Atlassian’s shared responsibility model says to use asUser() for an operation performed on a user’s behalf. Before an asApp() call, the app must verify the expected permissions. The platform handles parts of authentication; the developer still owns the application’s authorisation logic.

For the classification example, ask whether the action should succeed when the current user cannot edit the issue in Jira. Usually, it shouldn’t. That gives you a useful starting point for both implementation and testing.

App-context calls have legitimate uses, including background work. The important part is having a reason for the additional authority and a check that limits its use. “The other call returned 403” is a debugging observation, not that reason.

The Forge Authorize API provides helpers for checking user permissions before app-context requests. Choose the check for the actual operation and resource. Handle a failed check without making the change, and handle a later API refusal too: permissions can change between requests.

Test with someone who cannot do the job

An administrator account is convenient during development, but it hides permission mistakes. For this example, use at least these cases:

Account Attempt Expected result
Permitted project member Update an allowed issue Change succeeds
Read-only member Update that same issue Change is rejected
Member of a different project Submit a restricted issue ID Access is denied
User whose access was just removed Repeat a previously allowed change The next change is denied

Check the stored issue after each rejected request. An error response doesn’t help if the resolver changed the data before returning it.

Also change the issue ID sent by the browser. Hiding an option in the UI won’t stop a caller from submitting a different value. The resolver needs to validate the input and make its own permission decision. Atlassian’s resolver reference explains the context available to backend functions; use it carefully rather than trusting identity or permission claims in the payload.

Leave the next developer a useful explanation

Near each privileged operation, make the intent clear: which user or job initiated it, which resource it affects, and why app authority is needed. A short comment explaining that decision is more useful than one that merely repeats the API method name.

When a permission test fails, keep it as a regression case. It’s easy for a later refactor to preserve the successful workflow while accidentally removing the check that protected it.

References

Reviewed 10 November 2026. The classification app is a worked example.

Have a question about your application?

Talk to Actiotech →