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.
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));
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:
A 200 response but an empty values array:
Worked it out. There were 2 problems:
the "Authorization" header value is prefixed with "Bearer":
This needs to be "JWT".
After I made those 2 changes my GET request succeeded.
Nicely done. I've submitted and update to the docs and it should be deployed soon-ish.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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:
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:
But this raises 2 questions:
Tackling the 1st question first, I looked at the docs for GET request against a specific repo (see docs):
So I should not in fact need write or admin.
Turning to the 2nd question, I've read through Security overview & Scopes 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:
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 :(
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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:
The call was successful.
For the 2nd call I used an app password with these permissions:
This was the most permissive I could be, without granting read on repositories.
The call failed, as expected. But with this error message:
Note exactly sure what that means for my problem. But thought I would share incase someone smarter than I knows :)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 :)
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
My code in the original post has a constant called requestUrl.
After your initial suggestion, I was using the value:
I also tried:
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:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.