Marking call to BB from my Connect app
Asked by Alex Dess:

Context {#toc-hId--1272246966}
------------------------------

I'm building a BB Cloud Connect app. The app installs correctly, as in it receives tenant data such as ClientKey and SharedSecrect.

I'm now trying to have my app make API calls to BB on behalf of the tenant.

I started off with a proper, real-life implementation. But have now paired things back to a barebones example.

Steps to reproduce what I've done {#toc-hId-1215265867}
-------------------------------------------------------

* When my app is installed I save the following details
  * key
  * clientKey
  * sharedSecret
* I then use the following code to generate a JWT bearer token using the above data:

```
import * as jwt from "atlassian-jwt";
import moment from "moment";

const requestMethod = "GET"
const requestUrl = "https://api.bitbucket.org/2.0/repositories/XXX/?page=1&pagelen=100"
const key = "XXX"
const clientKey = "XXX"
const sharedSecret = "XXX"

const now = moment().utc();

const req: jwt.Request = jwt.fromMethodAndUrl(requestMethod, requestUrl);

const tokenData = {
    "iss": key,
    "iat": now.unix(),
    "exp": now.add(30, 'minutes').unix(),
    "qsh": jwt.createQueryStringHash(req),
    "sub": clientKey
};

console.log(jwt.encode(tokenData, sharedSecret));
```

* I then use the token generated in Postman to make a call to BB

Expected outcome {#toc-hId--592188596}
--------------------------------------

A list of repositories owned by the account, which is what I see when I make a call using Postman using basic auth and a BB app password:

![1.png](https://community.atlassian.com/t5/image/serverpage/image-id/130803i704FD9F7191F3F6A/image-size/large?v=v2&px=999 "1.png")

Actual outcome {#toc-hId-1895324237}
------------------------------------

A 200 response but an empty values array:

![2.png](https://community.atlassian.com/t5/image/serverpage/image-id/130804i8A871A5E3BC8AAF4/image-size/large?v=v2&px=999 "2.png")


Answers:
author: Alex Dess
body: Worked it out. There were 2 problems:

1. Despite [what the docs say](https://developer.atlassian.com/cloud/bitbucket/authentication-for-apps/#authentication-how-to) it appears as if you cannot submit the JWT token in the query string. It has to be in the header. I've informed BB support of this.
2. When you select Authorisation type as "Bearer Token" in Postman:

![1.png](https://community.atlassian.com/t5/image/serverpage/image-id/132376i499787F044D1B819/image-size/large?v=v2&px=999 "1.png")the "Authorization" header value is prefixed with "Bearer":

![2.png](https://community.atlassian.com/t5/image/serverpage/image-id/132377iDE66D9C07974E506/image-size/large?v=v2&px=999 "2.png")

This needs to be "JWT".

After I made those 2 changes my GET request succeeded.

comments: author: seanaty
body: Nicely done. I've submitted and update to the docs and it should be deployed soon-ish.


author: seanaty
body: Because accounts can have public repositories the response here can be confusing. This tells me that authentication is failing. You're only seeing the workspace's public repositories, which is, nothing :).

To troubleshoot authentication you can try fetching an individual repository that is private.  

As per why authentication is failing, I can't really see from your example anything that looks off. I would double check the claims in your JWT. Bitbucket can unfortunately be different from the rest of Atlassian. Here is the Bitbucket-specific documentation for JWT: <https://developer.atlassian.com/cloud/bitbucket/understanding-jwt-for-apps/>

It looks as if the \`iss\` claim also needs to be the clientKey (along with the \`sub\` claim).

Hope this helps!

comments: author: Alex Dess
body: Thanks for your reply :)

> It looks as if the \`iss\` claim also needs to be the clientKey (along with the \`sub\` claim).

I read the docs, it looks like from my reading fo that iss should be:

* clientKey - BB calling app
* key - app calling BB

I'm trying to the latter. But I tried out using clientKey just to see, but it didn't seem to make a difference. So I'm not sure that is id.

> To troubleshoot authentication you can try fetching an individual repository that is private.

This was super helpful. I changed it to fetching a private repo as suggested and now I get a 404 - ***Access denied. You must have write or admin access***.

So it definitely looks like a permission error and not an authentication one. That is supported by this screen that is shown when the app is installed:

![1.png](https://community.atlassian.com/t5/image/serverpage/image-id/131188i9D755EAD5AC0A44E/image-dimensions/335x222?v=v2 "1.png")

But this raises 2 questions:

1. Why does a GET request against a repo requires either write or admin permissions? Why not just read?
2. How do grant the app write or admin permissions? Note: Not desirable but may be helpful in understanding things.

Tackling the 1st question first, I looked at the docs for GET request against a specific repo (see [docs](https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D#get)):

![2.png](https://community.atlassian.com/t5/image/serverpage/image-id/131189i0DC2CF1241BEA6EC/image-dimensions/390x284?v=v2 "2.png")

So I should not in fact need write or admin.

Turning to the 2nd question, I've read through [Security overview](https://developer.atlassian.com/cloud/bitbucket/security-overview/) \& [Scopes for the Bitbucket Cloud REST API](https://developer.atlassian.com/cloud/bitbucket/bitbucket-cloud-rest-api-scopes/). I changed the [app descriptor](https://developer.atlassian.com/cloud/bitbucket/app-descriptor/) so that it now includes the scope "repository:admin". Now when the app is installed I see:

![3.png](https://community.atlassian.com/t5/image/serverpage/image-id/131192iE2FCB4CA12C5F71E/image-dimensions/395x261?v=v2 "3.png")

But I still get the same error when I make a call, 404 - Access denied. You must have write or admin access.

So I'm still at a bit of a loss as to what is wrong :(


author: Alex Dess
body: I tried one more thing. I performed 2 tests, both make calls via Postman using basic auth with an app password.

For the 1st call I used an app password with these permissions:

![1.png](https://community.atlassian.com/t5/image/serverpage/image-id/131197i98EC0F5C6B13D93D/image-dimensions/325x293?v=v2 "1.png")

The call was successful.

For the 2nd call I used an app password with these permissions:

![2.png](https://community.atlassian.com/t5/image/serverpage/image-id/131198i115783F9D60EA01D/image-dimensions/319x311?v=v2 "2.png")

This was the most permissive I could be, without granting read on repositories.

The call failed, as expected. But with this error message:

![3.png](https://community.atlassian.com/t5/image/serverpage/image-id/131199iBAC3640B4172558F/image-size/large?v=v2&px=999 "3.png")

Note exactly sure what that means for my problem. But thought I would share incase someone smarter than I knows :)


author: seanaty
body: > ***Access denied. You must have write or admin access*** .  

This is also the default when you request a private repository anonymously. Something is still misconfigured with how you're making the request it seems.

The scopes for your application are correct.


author: Alex Dess
body: Thanks, [@seanaty](/t5/user/viewprofilepage/user-id/1301071) based on your last message I refocused my efforts on request.

I've tried a number of things since then, none that have worked. But I'm going to list them with the hope that you might be able to narrow things down for me.

I've listed 3 things I changed, each with several variations. I tied combining a number of them, but I'm not sure I've covered every possible combination. If you could please help me narrow down the search space :)

#### JWT token in query param instead of Authorization header {#toc-hId--571059322}

Originally I was submitting the token generated using the **Authorization** header. Specifically like this:

```
Authorization: Bearer xxxxxxx
```

where "xxxxxxx" is the value returned by the code in my original post.

Instead of doing that I tried submitting the token using a query parameter. That is going from:

```
https://api.bitbucket.org/2.0/repositories/YYYY/ZZZ/
```

to

```
https://api.bitbucket.org/2.0/repositories/YYYY/ZZZ/?jwt=xxxxxxx
```

#### Variations on the values for requestUrl {#toc-hId-1916453511}

My code in the original post has a constant called **requestUrl**.

After your initial suggestion, I was using the value:

* <https://api.bitbucket.org/2.0/repositories/YYYY/ZZZ/>

I also tried:

* /2.0/repositories/YYYY/ZZZ/
* 2.0/repositories/YYYY/ZZZ/
* /repositories/YYYY/ZZZ/
* repositories/YYYY/ZZZ/

#### Using Java atlassian-jwt libary {#toc-hId-108999048}

You had linked to the page [Understanding JWT for apps](https://developer.atlassian.com/cloud/bitbucket/understanding-jwt-for-apps/). I had been using that page as my reference but I had not tried using the Java example provided. So I tried using that.

The example is actually for Jira, even though it is on a page about BB. So it was not super clear which variables in the example correspond to values from the tenant install. But I've had a bit of a guess and modified the code to this:

```
public static String createUriWithJwt(String sharedSecret, String clientKey)
        throws UnsupportedEncodingException, NoSuchAlgorithmException {
    long issuedAt = System.currentTimeMillis() / 1000L;
    long expiresAt = issuedAt + 180L;
    String key = "pipeline-stats2"; //the key from the app descriptor
    //during the app installation handshake
    String method = "GET";
    String baseUrl = "https://api.bitbucket.org";
    String contextPath = "/2.0";
    String apiPath = "/2.0/repositories/YYYY/ZZZZ/";

    
    JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder()
            .issuedAt(issuedAt)
            .expirationTime(expiresAt)
            .issuer(key)
            .subject(clientKey);

    CanonicalHttpUriRequest canonical = new CanonicalHttpUriRequest(method,
            apiPath, contextPath, new HashMap());
    JwtClaimsBuilder.appendHttpRequestClaims(jwtBuilder, canonical);

    JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory();
    String jwtBuilt = jwtBuilder.build();
    String jwtToken = jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256,
            sharedSecret).jsonToJwt(jwtBuilt);

    String apiUrl = baseUrl + apiPath + "?jwt=" + jwtToken;
    return apiUrl;
}
```

I'm not super sure I've used the correct value for the variables:

* baseUrl
* contextPath
* apiPath


Tags: bitbucket-cloud

cloud

connect