atlassian-connect-spring-boot: call bitbucket Rest Api

Hello! In this article we will talk about developing a Bitbucket Cloud app with atlassian-connect-spring-boot.

We will change the app developed here.

You can find the code for this article here.

Add admin page

We will delete the current module in backend/src/main/resources/atlassian-connect.json and add the following lines:

"modules": {
    "adminPages": [
        "url": "/bbpage",
        "location" : "org.bitbucket.account.admin",
        "name": {
          "value": "Bitbucket app"
        "key": "bb-page"
  "scopes": ["account", "repository", "pullrequest"],
  "contexts": ["account"]

It means that we will have our own page in the admin settings and this page will call the bbpage endpoint.

Now let’s add the bbpage endpoint.

Add the following lines into backend/src/main/java/ru/matveev/alexey/atlassian/cloud/tutorial/controller/

public String bbPage() {
  return "bbpage";

Now let’s create the bbpage template (backend/src/main/resources/templates/bbpage.html):

<!DOCTYPE html>
<html xmlns:th="">
    <script th:src="@{${atlassianConnectAllJsUrl}}" type="text/javascript"></script>
    <script type="text/javascript" src="/bundled.main.js" charset="utf-8"></script>
  <h1>BB App</h1>
  <button type="button" class="ak-button ak-button__appearance-default">Button created using the reduced-ui-pack</button>

Install and run the app

Run ngrok and create a tunnel. Put the proxy url into backend/src/main/resources/application.yml.

Package the app and run.

Now go to your workspace in and choose settings -> Develop Apps:

Push the register app button and add the proxy url from ngrok:

Push the Register app button:

Actually nothing happened yet on your app side. The client is not registered yet in your app. To register the client click on the installation url.

Choose the workspace and push the Grant access button:

Now your app is correctly installed and your app has registered the client.

Click on the Bitbucket app link:

Everyting works.

Call bitbucket rest api

Now let’s call a Bitbucket rest api from our app. Let’s change the bbPage method in backend/src/main/java/ru/matveev/alexey/atlassian/cloud/tutorial/controller/

public String bbPage() {
    String response = atlassianHostRestClients.authenticatedAsAddon().getForObject("/2.0/workspaces", String.class);
    return "bbpage";

We use atlassianHostRestClients as in the documentation for atlassian-connect-spring-boot. Run our app and we will have the following error: Server redirected too many  times (20)

Nice! What went wrong?

If we have a look at the docs for atlassian-connect-spring-boot, we will notice the following lines:

Provides a Spring Boot starter for building Atlassian Connect add-ons for JIRA (Software, Service Desk and Core) and Confluence.

Which means that we can not use this framework for Bitbucket Cloud. Obviously, we need to write our own framework!

But it is not true. All we need to do is to call Bitbucket Rest Api with the valid way for Bitbucket.

You can read more about it here. To tell you the truth I spent a way too much time to understand what this doc says. Also information is very misleading in the Bitbucket doc here.

Anyway to translate it to a simple language. First, you need to get a jwt token, then get a token using this jwt token and then use the token in the Authorization header.

I have not found any information on how to generate the jwt token. In the end I found a nodejs app and found in the code how to generate this jwt token.

Let’s do it in our app.

Add the following dependency in the pom.xml file:


Now create backend/src/main/java/ru/matveev/alexey/atlassian/cloud/tutorial/bitbucket/

public class AccessTokenResponse {
    String access_token;
    String scopes;
    String expires_in;
    String token_type;

Then backend/src/main/java/ru/matveev/alexey/atlassian/cloud/tutorial/bitbucket/

public class JWTGenerator {
    private final String sharedSecret;
    private final String clientKey;
    private final String appKey;
    private Long expireIn = 18000L;
    public String getJWT() {
        long issuedAt = System.currentTimeMillis() / 1000L;
        long expiresAt = issuedAt + expireIn;
        String jwt = new JsonSmartJwtJsonBuilder()

        JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory();
        return  jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256,


    public JWTGenerator(String sharedSecret,
                        String clientKey,
                        String appKey,
                        Long expireIn) {
        this(sharedSecret, clientKey, appKey);
        this.expireIn = expireIn;

That is the correct way to generate a jwt token.

And at last let’s get a token based on the jwt token backend/src/main/java/ru/matveev/alexey/atlassian/cloud/tutorial/bitbucket/

public class AccessToken {
    private final String sharedSecret;
    private final String clientKey;
    private final String appKey;
    private Long expireIn;

    public AccessTokenResponse getAccessToken() {
        RestTemplate restTemplate = new RestTemplate();
        JWTGenerator jwtGenerator = expireIn == null ? new JWTGenerator(this.sharedSecret, this.clientKey, this.appKey) : new JWTGenerator(this.sharedSecret, this.clientKey, this.appKey, this.expireIn);
        String jwt =jwtGenerator.getJWT();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "JWT " + jwt);

        MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
        map.add("grant_type", "urn:bitbucket:oauth2:jwt");

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);

        ResponseEntity<AccessTokenResponse> response
                = restTemplate.postForEntity("", request, AccessTokenResponse.class);

        return response.getBody();


Now let’s get this token in our bbPage method and call Bitbucket Rest Api:

public String bbPage(@AuthenticationPrincipal AtlassianHostUser hostUser) {
                AccessTokenResponse accessToken = new AccessToken(hostUser.getHost().getSharedSecret(), hostUser.getHost().getClientKey(), "hello-world-app").getAccessToken();

                RestTemplate restTemplate = new RestTemplate();
                HttpHeaders headers = new HttpHeaders();
                headers.add("Authorization", "Bearer " + accessToken.getAccess_token());
                HttpEntity<String> request = new HttpEntity<String>(headers);
                ResponseEntity<String> response ="", HttpMethod.GET, request, String.class);
                String account = response.getBody();
                return "bbpage";

We get hostUser which contains information about the client who called our bbpage. Then we get the token and use it in the Authorization header.

That is all. The Rest Api call worked this time.

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Leave a Reply

%d bloggers like this:

Spelling error report

The following text will be sent to our editors: