Last updated Feb 21, 2023

Multitenancy

As you think about building apps for Atlassian cloud products, you'll need to handle data in a multitenant environment.

Atlassian apps in the cloud are different from the server environment, where each instance of an app is installed on just one tenant. As an app developer, you only need to worry about the data you’re given on that instance.

In the cloud, your app is hosted and accessed through multiple Atlassian cloud instances that have installed your app. You need to securely handle and save the data for each tenant.

Multitenancy is handled differently in Forge and Connect. This page outlines what you’ll need to consider for both.

Multitenancy in Forge

The Forge platform enables secure data management through its architecture and the way it handles data. This includes data isolation to prevent leaks as well as data handling policies for the Forge environments.

The Forge platform sandboxes apps, as described in the App runtime section. Since apps are sandboxed, app calls occur in separate instances of the app. This means that data is isolated at the runtime level, preventing data from leaking.

Forge builds security directly into the platform to prevent data leaking to other tenants so that you don’t have to worry about it while developing your app.

Multitenancy in Connect

Before you write one line of code, it’s good to consider how to manage data in your app with multiple tenants. Here are some things to consider when architecting your app:

  • Identify and save the tenant information into your persistent storage
  • Validate that requests and data you receive from the product match the expected tenant
  • Use the tenant as a foreign key with any and all data you save in your persistent storage

Getting the tenant information

When a customer installs your app in the Atlassian product, the product reads the app descriptor and sends the tenant client information to the installed lifecycle webhook you’ve specified.

1
2
// app-descriptor.json
{
  ...
  "lifecycle": {
    "installed": "/installed"
  }
  ...
}

Your app should have an endpoint at /installed and expect data to be POSTed to it. Here’s is an example of the data object that will be sent:

1
2
{
  "key": "installed-addon-key",
  "clientKey": "unique-client-identifier",
  "sharedSecret": "a-secret-key-not-to-be-lost",
  "serverVersion": "server-version",
  "pluginsVersion": "version-of-connect",
  "baseUrl": "https://example.atlassian.net",
  "displayUrl": "https://docs.example.com",
  "productType": "jira",
  "description": "Atlassian Jira at https://example.atlassian.net",
  "serviceEntitlementNumber": "SEN-number",
  "eventType": "installed"
}

When you receive this data, save it to your persistent data store and index against the clientKey as the unique identifier.

Validating requests

Because every communication from the Atlassian product contains the clientKey in the JSON Web Token (JWT), you can use the clientkey to verify that the request is coming from the expected tenant. You use the clientKey and sharedSecret to create a JWT to validate data to and from the Atlassian product.

Saving app data

If your app needs to store any other data, you now have the tools to securely bind that data to the appropriate tenant:

  1. The Atlassian product sends the clientKey in each JWT.
  2. You decode the token and extract the clientKey.
  3. You use the clientKey to verify that the data in your datastore belongs to the tenant requesting access.
  4. You save every piece of data using the tenant’s clientKey as a foreign key.

Rate this page: