Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Marking call to BB from my Connect app

Context

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

  • 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

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

Actual outcome

A 200 response but an empty values array:

2.png

2 answers

1 accepted

1 vote
Answer accepted

Worked it out. There were 2 problems:

  1. Despite what the docs say 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.pngthe "Authorization" header value is prefixed with "Bearer":

2.png

 

This needs to be "JWT".

 

After I made those 2 changes my GET request succeeded.

seanaty Atlassian Team Mar 01, 2021

Nicely done. I've submitted and update to the docs and it should be deployed soon-ish.

Like Alex Dess likes this
0 votes
seanaty Atlassian Team Feb 18, 2021

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!

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

 

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):

2.png

So I should not in fact need write or admin.

 

Turning to the 2nd question, I've read through Security overviewScopes for the Bitbucket Cloud REST API. I changed the app descriptor so that it now includes the scope "repository:admin". Now when the app is installed I see:

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 :(

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

 

The call was successful.

 

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

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

 

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

seanaty Atlassian Team Feb 19, 2021

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.

Like Alex Dess likes this

Thanks, @seanaty 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

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

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

After your initial suggestion, I was using the value:

 I also tried:

  • /2.0/repositories/YYYY/ZZZ/
  • 2.0/repositories/YYYY/ZZZ/
  • /repositories/YYYY/ZZZ/
  • repositories/YYYY/ZZZ/

 

Using Java atlassian-jwt libary

You had linked to the page 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

Suggest an answer

Log in or Sign up to answer
TAGS
Community showcase
Published in Bitbucket

New improvements to user management in Bitbucket Cloud 👥

Hey Community! We’re willing to wager that quite a few of you not only use Bitbucket, but administer it too. Our team is excited to share that we’ll be releasing improvements throughout this month of...

3,719 views 10 16
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you