I’m building a Forge app (Jira global page, Custom UI) that needs to call
a second, admin-chosen, Jira Cloud site’s REST API using Basic Auth
(service-account email + API token entered by the admin) — not the site
the app is installed on.
Manifest (relevant parts):
permissions:
external:
configurable:
enabled: true
fetch:
backend:
- remote: target-site
remotes:
- key: target-site
baseUrl: "https://placeholder.atlassian.net"
operations:
- fetch
configurable:
name: Target Jira site
description: "The Jira Cloud site that receives synced issues."
supportedPatterns:
- "*.atlassian.net"
Frontend consent flow (Custom UI, @forge/bridge):
await permissions.remote.set({
remotes: [{ key: 'target-site', configured: { endpoint: targetSiteUrl } }],
});
permissions.remote.get({ keys: ['target-site'] }) correctly confirms the
endpoint is stored, e.g.:
[{"key":"target-site","configured":{"endpoint":"https://my-target.atlassian.net"}}]
Attempt 1 — plain fetch() from @forge/api to the literal consented URL:
await fetch(`${targetSiteUrl}/rest/api/3/myself`, {
headers: { Authorization: `Basic ${auth}` },
});
Always fails, immediately and even after several minutes (ruled out
propagation delay):
403
URL not included in the external fetch backend permissions: https://my-target.atlassian.net.
Visit go.atlassian.com/forge-egress for more information.
…despite permissions.remote.get confirming the consent is stored for
exactly that URL, and the manifest referencing the remote under
fetch.backend.
Attempt 2 — invokeRemote() instead:
await invokeRemote('target-site', {
path: '/rest/api/3/myself',
method: 'GET',
headers: { Authorization: `Basic ${auth}` },
});
This one does reach the target site (no more egress 403) but the target
responds 401 Client must be authenticated to access this resource. Per
your own docs (“Forge Remote essentials”),
the authorization header is always attached by Forge as a required
header carrying the Forge Invocation Token (FIT) as a bearer token — which
appears to silently override whatever Authorization header I set myself,
so my Basic Auth credentials never reach the target.
The question is: is there a supported way to make a plain fetch() honor a
customer-managed remote’s runtime-configured (admin-consented) endpoint,
so I can set my own Authorization header? Or is invokeRemote +
“host your own backend that verifies the FIT and does the real outbound
call” the only supported pattern for this — i.e. is calling a third
party’s API (in this case, another Atlassian Cloud site) directly with
app-supplied credentials via a customer-managed remote simply not
supported today, and a self-hosted proxy is required?
Forge CLI 13.5.0, @forge/api 8.0.5, @forge/bridge 6.3.1.