Last updated Apr 25, 2024

Forge remote essentials

There are some key considerations and responsibilities when using Forge remote.

Increased responsibility

If you decide to integrate remote services with Forge you take on additional responsibilities under the Forge Shared responsibility model.

Remote contract

This section defines the contract used by remote requests including HTTPS protocol level details, request and response schema.

UI modulesEvents and scheduled triggers
API ArchitectureRESTREST
ProtocolHTTPSHTTPS
MethodsGET, POST, PUT, PATCH, DELETEPOST
Timeout25s5s
RetriesNone4 (See Handing retries for more details)
Response headers Content-Type: application/json

Custom headers: You can also return custom headers in your API response.

Method usage

When using invokeRemote in your Forge app's frontend to make requests to a remote backend, you specify the method and path for the endpoint in the invokeRemote call.

When using Forge remote with events or a scheduled trigger, the request is automatically routed to the endpoint configured in the manifest using the POST method.

IP address ranges used when invoking remote backends from Forge

When communicating with remote backends, Forge uses IP address ranges as indicated in the Outgoing Connections section of IP addresses and domains for Atlassian cloud products. This information may be important if your app's remote backend is behind a firewall or other network equipment that restricts access based on IP address.

Request headers

The following headers are added to requests to your remote.

HeaderRequiredDescription
x-b3-traceidYesThe TraceId is 64 or 128-bit in length and indicates the overall ID of the trace. Every span in a trace shares this ID.
x-b3-spanidYesThe SpanId is 64 or 128-bit in length and indicates the position of the current operation in the trace tree. The value should not be interpreted: it may or may not be derived from the value of the TraceId.
authorizationYesThe Forge Invocation Token (FIT) is passed as a bearer token.
x-forge-oauth-systemNoThe app system token. This is used to call Atlassian APIs from your remote backend. This header is included only if endpoint.auth.appSystemToken is true in the app's manifest.
x-forge-oauth-userNoThe app's user token. This is used to call Atlassian APIs from your remote backend on behalf of a user. This header is included only if endpoint.auth.appUserToken is true in the app's manifest.

As an example:

1
2
x-b3-traceid: a523b7549f0b88c9
x-b3-spanid: 2a2436c64727923f
authorization: Bearer ${FIT}
x-forge-oauth-system: ${OAuth-System-Token} 
x-forge-oauth-user: ${OAuth-User-Token}
header1: value ## Set customer headers when invoking your remote from a UI module

Errors

Requests that exceed the timeout or do not return a 2xx HTTP Status Code, will be considered as a failure and will appear in the developer console metrics. See Remote observability for more on metrics and logging.

HTTP Status CodeDescription
2xxThe remote successfully processed the request.
3xxRedirects are not supported and are treated as an error status code.
401The JWT token validation failed.

The Forge Invocation Token (FIT)

Requests to your remote backend will include a Forge Invocation Token (FIT) as a bearer token in the authorization header. The FIT includes important information about the invocation context and should be used to verify that the request came from Atlassian and is intended for your app.

The Forge Invocation Token contains a JSON object with the following properties:

PropertyTypeRequiredDescription
appobjectYesInformation about the app and installation context.
app.installationIdstringYesIdentifier for the specific installation of an app. This is the value that any remote storage should be keyed against.
Example: ari:cloud:ecosystem::installation/75969db9-dc7b-4798-9715-bd098ac0d9d1
app.apiBaseUrlurlYesAPI base URL where all product API requests should be routed.
Example: https://api.atlassian.com/ex/confluence/4c822e2f-510f-48b9-b8d2-8419d0932949
app.idstringYesThe Forge application ID. This should match the value in your Forge manifest.yml file.
Example: ari:cloud:ecosystem::app/77334c21-3dd0-474f-a53f-28b4eeee5a71
app.versionnumberYesThe Forge application version being invoked.
Example: 1
app.environmentobjectYesInformation about the environment the app is running in.
app.environment.typestringYesThe Forge environment type that this invocation was generated for.
Examples: DEVELOPMENT, STAGING, PRODUCTION
app.environment.idstringYesThe Forge environment id that this invocation was generated for.
Example: ari:cloud:ecosystem::environment/3bb5deab-afcd-4140-9be4-f837b4b14b2c
app.moduleobjectYesInformation about the module that initiated this remote call.
app.module.typestringYesThe module type initiating the remote call. This will be the module type, such as xen:macro for front-end invocations. Otherwise, it will be core:endpoint. To determine the type of module that specified the remote resolver, see payload.context.extension.type.
Example: core:endpoint
app.module.keystringYesThe Forge module key for this endpoint.
Example: forge-remote-app-boot
contextobjectThe context depends on how the app is using Forge Remote:
principalstringThe identifier for the user who invoked the app. UI modules only.

For example:

1
2
{
  "app": {
    "id": "ari:cloud:ecosystem::app/8db33809-1f32-48bb-8c52-5877dab48107",
    "version": "16",
    "installationId": "ari:cloud:ecosystem::installation/0a3a7799-53ae-4a5b-9e7e-03338980abb5",
    "apiBaseUrl": "https://api.stg.atlassian.com/ex/confluence/d0d52620-3203-4cfa-8db5-f2587155f0dd",
    "environment": {
      "type": "DEVELOPMENT",
      "id": "ari:cloud:ecosystem::environment/8db33809-1f32-48bb-8c52-5877dab48107/aa911f10-c54b-4b93-9e27-dd2947840b9e"
    },
    "module": {
      "type": "xen:macro",
      "key": "forge-remote-app-boot"
    }
  },
  "context": {
    "localId": "4654fa12-4c7c-4792-95a9-6019edb27953",
    "cloudId": "d0d52620-3203-4cfa-8db5-f2587155f0dd",
    "moduleKey": "forge-remote-app-boot",
    "siteUrl": "https://pbray2.jira-dev.com",
    "extension": {
      "type": "macro",
      "content": {
        "id": "5341185"
      },
      "space": {
        "key": "~655362312d3308895442b0aa38771a10c88656",
        "id": "65538"
      },
      "isEditing": false,
      "references": []
    }
  },
  "principal": "655362:312d3308-8954-42b0-aa38-771a10c88656",
  "aud": "ari:cloud:ecosystem::app/8db33809-1f32-48bb-8c52-5877dab48107",
  "iss": "forge/invocation-token",
  "iat": 1700175149,
  "nbf": 1700175149,
  "exp": 1700175174,
  "jti": "d8a496253ec8c18a54631e4c82cbedd5d0ae8570"
}

Verifying remote requests

If you implement a remote backend, you take on additional responsibilities under the Forge Shared responsibility model.

You must ensure that you:

  • Validate all JWT Forge Invocation Tokens provided to your remote endpoint in an authorization header against the following JWKS to ensure that they originated from Atlassian Forge and were intended for an audience of your Application ID.
  • Never share or log any OAuth access tokens provided.
  • Avoid storing OAuth access tokens unless absolutely necessary.

To help illustrate the requirements for the above, here is some sample code in both Javascript and Java, which is available from the linked repos.

Example code in Java:

1
2
@Component
public class FITValidator {

    @Value("${jwks.endpoint:https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json}")
    private String jwksUrl;

    @Value("${appId}")
    private String appId;

    public void validate(String invocationToken) throws InvalidJwtException {
        var httpsJwks = new HttpsJwks(jwksUrl);
        var httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJwks);
        var jwtConsumer = new JwtConsumerBuilder()
                .setVerificationKeyResolver(httpsJwksKeyResolver)
                .setExpectedAudience(appId)
                .setExpectedIssuer("forge/invocation-token")
                .build();

        jwtConsumer.process(invocationToken);
    }
}

Example code in Javascript:

1
2
export const validateContextToken = async (invocationToken, appId) => {
  const jwksUrl = 'https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json';
  const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));

  const payload = await jose.jwtVerify(invocationToken, JWKS, {audience: appId});
  return payload;
}

Data residency eligibility

When an app contains a remotes declaration, Forge will (by default) assume the app is storing in-scope end-user data on a remote backend. This makes the app ineligible to be PINNED, which is a status that admins use to assess an app's data residency compliance.

To be eligible for this PINNED status, you must explicitly declare that your remote backend isn't used for storing in-scope End-User Data. See Remotes for specific instructions on how to do this in your manifest.

For more details about in-scope End-User Data and the PINNED status for apps, see Data residency.

Reference materials

Forge remote makes particular use of some manifest.yml sections and properties, runtime APIs, and data structures you might not have encountered before.

Content areaReference
Forge remote
reference
Other related reference

Rate this page: