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

Marking call to BB from my Connect app

Alex Dess February 17, 2021

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
Alex Dess February 28, 2021

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
Atlassian Team members are employees working across the company in a wide variety of roles.
March 1, 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
Atlassian Team members are employees working across the company in a wide variety of roles.
February 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!

Alex Dess February 18, 2021

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

Alex Dess February 18, 2021

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
Atlassian Team members are employees working across the company in a wide variety of roles.
February 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
Alex Dess February 22, 2021

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
AUG Leaders

Atlassian Community Events