Getting always "Unauthorized; scope does not match" in Expose Forge app REST APIs

I am trying to use the Forge app REST API preview feature with a Confluence Forge app, and I followed all the documented setup steps. The OAuth authorization flow succeeds, and the access token I receive includes the custom scopes I defined in my Forge manifest, such as read:employee:custom and write:employee:custom.

However, when I send a request to the exposed REST API, I get:

401 Unauthorized; scope does not match

Because the token is issued successfully and contains my custom scopes, I suspect the problem may be related to the Forge app scope configuration or the way the required Forge app permissions are being handled in the OAuth 2.0 / 3LO setup.

Maybe it’s because of the step 7, in fact it’s the only missed step in the docs which tells us to add the read:forge-app:confluence scope

but I can’t find it in the scopes configuration section of the 3LO app

Welcome to the Atlassian developer community @MohamedFaroukAyadi,

I hit the same thing while trying to write a sample app for apiRoute: ibuchanan/explore-forge-jira-custom-api

It turns out there’s “semi-documented” parameter that clients need to pass when using this feature. The Atlassian developer team thought that users would find the correct URL in the developer console: See this screenshot

For me, the dev console generates this URL for the “Marketplace App” (even though my app is not on Marketplace):

https://auth.atlassian.com/authorize?
audience=api.atlassian.com&
client_id=dv7a3bGNJ2DmfTvCcjmOAresMsIuChCy&
scope=write%3Aworkitem-as-user%3Acustom%20write%3Aworkitem%3Acustom%20read%3Aforge-app%3Ajira&
redirect_uri=http%3A%2F%2Flocalhost%3A9876%2Fcallback&
state=${YOUR_USER_BOUND_VALUE}&
response_type=code&
prompt=consent&
sns=bb398628-d837-4378-9fe5-9b033fb24aac.ff091058-d87e-4e6d-b426-ec93e1121618

Newlines added for readability. Notice the sns parameter. It’s needed to tie the resulting OAuth token to your specific Forge app installation and it mirrors the URL you would call for for requests:

https://<site>.atlassian.net/gateway/api/svc/jira/apps/<app-id>_<env-id>/workitem

For reference, see this helper Python code for doing the OAuth dance using the relationship between the URL and the sns parameter: bootstrap-oauth.py

I discussed with the Atlassian team and thought they were going to update the docs. If there isn’t a PR on those docs already, I’ll update myself soon.

Hello ibuchanan,

Thanks for the earlier response.

I’m seeing an issue with the auto-generated authorization URL for my Forge app. The URL already contains an sns parameter, and I found the related ticket: https://jira.atlassian.com/browse/ECO-1289. According to the docs, the URL should include the scope read:forge-app:confluence, but in my case that scope is missing.

I attempted to add read:forge-app:confluence manually to the authorization URL, but the modified URL did not work. Could the missing read:forge-app:confluence scope be the reason the authorization fails, or is there something else I might be overlooking? Any guidance on why the auto-generated URL omits this scope and how to ensure it’s included would be appreciated.

Thanks,
Mohamed Farouk Ayadi

Yes. That will also result in 401 Unauthorized.

It’s implicit. And it’s bound to a specific product based on the Forge app’s install context. Hence, I don’t think the developer console can predict which one you need (Jira or Confluence).

To be honest, I’m not sure how those auto-generated URLs are expected to be used. I’ve never seen a client UI where it accepts a whole URL via copy/paste. In my experience, both UI and SDKs require specification of the parts, often because some aspects have to be used in multiple places. For example, the state parameter obviously needs to be filled out by client code, not a human.

In any case, my observation is the URLs need to be “merged”. I frequently use scopes from multiple APIs in 1 client, so I can’t use multiple separate URLs from the developer console.

Could you share the URL you’ve tried? It’s safe because you aren’t sharing the client_secret. And, until you use the “distribution” option, the flow will fail for everyone except you.

Hello Ibuchanan,

this is the url I tried :
https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=client_id&scope=read%3Aforge-app%3Aconfluence read%3Aemployee%3Acustom write%3Aemployee%3Acustom&redirect_uri=https%3A%2F%2Fimpromptu-choosing-exclusion.ngrok-free.dev%2Fpublic%2Fcallback&state=${YOUR_USER_BOUND_VALUE}&response_type=code&prompt=consent&sns=5ecda1fb-028f-4018-b877-f64191da25f5.e7a7e996-ae71-4fe2-911f-962dcabd34f8

And I get this :

it seems that the 3LO app is required to grant the permission to the scope, but I cant find where and how to do it

@MohamedFaroukAyadi,

I was wrong when I said, “It’s implicit.” Here’s where you have to enable it in the developer console:

Hello Ibuchanan, It works as you described, and the authentication is now being completed correctly.

However, I’m facing two other challenges:

  1. I couldn’t find any way in the docs to extend the expiration time of the access token. Is it possible to customize the access token lifetime, or is it fixed by design?

  2. In the handler for the exposed API, I’m calling invokeRemote, but it fails with a 424 error:
    Invocation failed for appId: unknown

This happens despite having what appears to be a correct configuration. The same invokeRemote call works in other handlers, such as webtriggers and resolvers, so I suspect the issue may be related to the exposed REST API feature itself.

This is the manifest remote configuration :

remotes:
- key: lms-remote
baseUrl: https://interrogatingly-granitic-britney.ngrok-free.dev
auth:
appSystemToken:
enabled: true
appUserToken:
enabled: true
operations:
- fetch
- compute

Best regards,

Hey @MohamedFaroukAyadi,

Correct. Access tokens are intended to be short-lived both in the OAuth spec and for Forge specifically. You should probably look at implementing the refresh flow.

Could you start a new thread about this?