Last updatedSep 20, 2019

API proxy module

The API proxy module creates a layer between your API and Bitbucket to provide the heavy lifting for authentication, parameter substitution, object hydration, and other services. This should make the API integrations and apps you build into Bitbucket Cloud more efficient and easier to build.

You create the proxy connection when you map a URL pattern to a specific destination URL in your app descriptor. The simplest form of this is:

1
2
3
4
5
6
7
8
9
10
"modules": {
    "proxy": {
        "/proxy-example/{target_user}/{repository}": {
            "destination": "/proxy-example?name={repository.owner.display_name}", // required
            "methods": { ... }, // optional
            "conditions": { ... }, // optional
            "scopes": { ... } // optional
        }
    }
}

Here /proxy-example/{target_user}/{repository} is the path available on Bitbucket, and would be the path you would use in calls to an AP object, for example. The destination URL is a path served by your server, and maps to a specific Bitbucket resource, in this case the display name of the repository owner.

Before you begin

It's important to understand these core concepts before you jump in to using the API proxy module.

Looking for reference doc or examples?

Reference: Find the elements, definitions, and properties of the API proxy module in the API proxy module reference docs.

Examples: Example configurations that demonstrate how to use the proxy module in your app descriptor can be found in the Examples section.

How it works

The following diagram depicts the basic operation of proxy module, from authenticating the client call to hydrating the response object.

The proxy module data flow Data flow of the proxy module

When a client initiates a call:

  1. The client for your app calls the API proxy (api.bitbucket.io).
  2. The proxy performs any user authentication, permission checks, or parameter substitution required.
  3. The proxy forwards the request to your app's backend service.
  4. Your app processes the request then sends a response back to the proxy.
  5. The proxy hydrates any response objects as required.
  6. The hydrated, augmented response is sent to the client.

How authentication works with the proxy module

Traditionally apps built for Bitbucket would have to verify the permissions for every request sent from your app. Now you can delegate some of that responsibility to Bitbucket. The proxy module lets Bitbucket provide user credentials, check permissions, and forward the authenticated requests to your app’s backend service.

As an added benefit, the proxy module allows you to expose the APIs of your service to be called from outside of Bitbucket and still rely on Bitbucket to authenticate and authorize any incoming requests.

Examples

These examples demonstrate how to use some of the main features of the proxy module.

Object hydration

Object hydration allows your app to respond to proxied API requests with a minimal amount of data for a resource and Bitbucket will fill in the rest of the data from the Bitbucket database. For complete details, see the page about Object hydration.

For example, let’s say you want to build an app that will display an example of pull request best practices. The app could display an existing pull request that demonstrates good pull request techniques. This exemplary pull request would then be displayed on a repo page in a repository.

The proxy module definition to support this might look like this:

1
2
3
4
5
"modules": {
  "proxy": {
    "/exemplary-pullrequest/{repository}": {
      "destination": "/exemplary-pullrequest?repository_uuid={repository.uuid}"
     }

In the previous example, the path definition is capturing the {repository} context parameter. Capturing context parameters like this allows them to be reference as nested properties of the captured parameters in the destination property in a similar fashion.

For objects that belong to another object (for example, a repository belongs to either a user or team) a parent object may need to be resolved in order to resolve the child object. For this example, the app is only capturing the {repository} object, which works provided a UUID is used to capture the repository object. If instead a repository slug was used in the ingress URL, the URL would also need to capture the {target_user} object, representing the account that owns the repository. This is because a repository slug doesn't uniquely identify a repository across accounts.

To get a better understanding of the parent-child object relationship, see the article about Object hydration.

The currently logged in user is always available to the destination of a proxy resource as the {user} parameter. You don't need to capture it at the ingress URL.

To further demonstrate how the proxy module works let's configure the app to make a rest request from an iFrame to retrieve data from this endpoint using AP.proxyRequest(). The request from the repo page iFrame would look like this:

1
2
3
4
5
6
7
8
AP.require('proxyRequest', function(request) {
    request({
        url: '/exemplary-pullrequest/{aa5acc33-e5f7-43e9-883d-50325fc68ca8}',
        success: function(pullrequest) {
            // render the exemplary pull request object
        }
    });
});

The full URL targeting the proxy would look like this:

1
href = "https://api.bitbucket.io/addons/myappkey/uuid/exemplary-pullrequest/{aa5acc33-e5f7-43e9-883d-50325fc68ca8}"

Requests can be made against the proxy from outside of Bitbucket using the same authentication methods used to call other Bitbucket APIs. And uuid is the UUID of the account the app is installed into.

In the preceding example, {aa5acc33-e5f7-43e9-883d-50325fc68ca8} is the UUID of the repository where the app is installed.

The handler in the backend service for the app would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.get('/exemplary-pullrequest', addon.authenticate(), (req, res) => {
  const repositoryUUID = req.query.repository_uuid;
  const pullRequestID = getExemplaryPullRequest(repositoryUUID);
  res.json({
    'type': 'pullrequest',
    'id': pullRequestID,
    'destination': {
      'repository': {
        'type': 'repository',
        'uuid': repositoryUUID,
      }
    }
  });
});

The handler in the preceding example takes the UUID from the parameter in the request (repository_uuid) and looks up the pull request ID for the exemplary-pullrequest. The app only needs to send and store ID's for the repository and pull request because the proxy will hydrate the pull request object as the response is passed back through the proxy to the client.

The proxy would see a response body like this:

1
2
3
4
5
6
7
8
9
10
{
  "type": "pullrequest",
  "id": 5683,
  "destination": {
    "repository": {
      "type": "repository",
      "uuid": "{aa5acc33-e5f7-43e9-883d-50325fc68ca8}",
    }
  }
}

The proxy module recognizes the object in the previous example as one it can hydrate. It reads the repository and pull request IDs in the object and produces a full pull request object that can be rendered in the repo page iFrame.

The API proxy will return an object as-is if it was unable to hydrate the object. This can happen when the object no longer exists (for example, a deleted repository) or when the request doesn't have the correct permissions.

Restrict HTTP methods

Following on from the last example, let's configure this example app to expose an API publicly that allows clients to set, or remove, an exemplary pull request for a repository.

To do this you would need to modify your proxy module definition like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
"modules": {
  "proxy": {
    "/exemplary-pullrequest/{repository}": {
      "methods": {
        "get": {
          "destination": "/exemplary-pullrequest?repository_uuid={repository.uuid}",
          "conditions": [{
            "condition": "has_permission",
            "params": {
              "permission": "read"
            },
            "target": "repository"
          }],
          "scopes": [
            "pullrequest"
            "repository"
          ],
        },
        "post": {
          "destination": "/exemplary-pullrequest/create?repository_uuid={repository.uuid}"
        },
        "delete": {
          "destination": "/exemplary-pullrequest/delete?repository_uuid={repository.uuid}"
        }
      }
      "conditions": [{
        "condition": "has_permission",
        "params": {
          "permission": "write"
        },
        "target": "repository"
      }],
      "scopes": [
        "repository:write"
        "pullrequest:write"
      ],
    }

The preceding example takes the existing endpoint and overrides the behavior for the GET, POST, and DELETE methods and redirects each method to a downstream resource which is able to handle that specific action. Within each method block you can configure the downstream communication with the backend app service.

You can use scopes and conditions within your proxy definition to configure permissions for access to resources. In the previous example, you can see that write permissions are configured for top level of the proxy definition.

1
2
3
4
5
6
7
8
9
10
11
"conditions": [{
  "condition": "has_permission",
  "params": {
    "permission": "write"
  },
  "target": "repository"
}],
"scopes": [
  "pullrequest:write"
  "repository:write"
],

However this behavior is overridden for the GET resource so that only read permissions and scopes are required.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"methods": {
  "get": {
    "destination": "/exemplary-pullrequest?repository_uuid={repository.uuid}",
    "conditions": [{
      "condition": "has_permission",
      "params": {
        "permission": "read"
      },
      "target": "repository"
    }],
    "scopes": [
      "pullrequest"
      "repository"
    ],
  },
  ...
}

By overriding the scopes for the GET method, your client can share the exemplary pull request with anyone with read permissions and scopes on the repository, but restrict the POST and DELETE methods.

Because the PUT method isn't configured, the proxy would return a 405 (method not allowed) if a client tries to PUT to this URL.

A client could POST a new exemplary pull request using the following request:

1
2
3
curl -H "Authorization:<authheader>" \
     -XPOST "https://api.bitbucket.io/addons/myappkey/uuid/exemplary-pullrequest/{aa5acc33-e5f7-43e9-883d-50325fc68ca8}" \
     -d '{ "id": 5683 }'

In this example, you'd only need to POST the ID for a pull request, since this is all the app needs to store, the proxy will take care of hydrating the pull request object if the client asks for it later with a GET.