Bitbucket get access token from refresh token help

Hi,

I posted this a while back about getting a renewed access token for a user through a refresh token. Specifically, I’m trying to do that with the provider - bitbucket.

I was referred to this documentation, which I implemented, but it’s not working.

Perhaps I’m doing it wrong. I also looked at the bitbucket documentation, which you can retrieve a new access token with:

curl -X POST -u “client_id:secret”
https://bitbucket.org/site/oauth2/access_token
-d grant_type=refresh_token -d refresh_token={refresh_token}

However, this is the client_id and secret I assume is for the consumer, which is Auth0 I assume? How do I get this info? Or do I rely on Auth0 to do this refresh process for me?

It’s a bit confusing because though I have a refresh token, I’m supposed to use my auth0 domain and domain client info to get a new access token for a specific user? I’m not sure it that makes sense to me. This is my implementation essential:

(1) set AUTH0 client info

  • set auth0 client id
  • set auth0 client secret
  • set auth0 domain
  • retrieve auth0 client token

(2) get initial user info

  • get user info at endpoint:
  • https://<auth0_domain>.auth0.com/api/v2/users/<user_id>
  • this will contain an access token for the user
  • this will also contain a refresh token for the “user” not the AUTH0 client (I assume?)

(3) try to get access token from refresh token

  • use the refresh token to get an access token here: https://<auth0_domain>.auth0.com/oauth/token

  • notably, the refresh token is for the user, but the client info is from the AUTH0 client - doesn’t make sense to me. this fails with this error.

{u’error_description’: u’Unauthorized’, u’error’: u’access_denied’}

e.g. in code

    payload = { "client_id": self.auth0_client_id,
                "client_secret": self.auth0_client_secret,
                "refresh_token": kwargs["refresh_token"].decode("utf-8"),
                "grant_type": "refresh_token" }

    _endpt = 'https://{}.auth0.com/oauth/token'.format(self.auth0_domain)
    headers = {'content-type': 'application/json'}

    req = requests.post(_endpt,
                        data=json.dumps(payload),
                        headers=headers)

Here is the code essentially:

 #!/usr/bin/env python

import json
import os
import requests
import logging

class Auth0Client(object):

    def __init__(self,**kwargs):

        self.classname = "Auth0Client"
        self.logger.logging.basicConfig(level=logging.INFO)

        # (1) set AUTH0 client info

        self.auth0_client_id = os.environ["AUTH0_CLIENT_ID"]
        self.auth0_client_secret = os.environ["AUTH0_CLIENT_SECRET"]
        self.auth0_domain = os.environ["AUTH0_DOMAIN"]
        self._set_auth0_client_token()

    def _set_auth0_client_token(self):

        payload = { "client_id": self.auth0_client_id,
                    "client_secret": self.auth0_client_secret,
                    "audience": "https://{}.auth0.com/api/v2/".format(self.auth0_domain),
                    "grant_type": "client_credentials" }

        api_endpoint = 'https://{}.auth0.com/oauth/token'.format(self.auth0_domain)

        req = requests.post(api_endpoint,
                            data=json.dumps(payload),
                            headers={'content-type': 'application/json'})

        self.auth0_client_token = req.json()["access_token"].decode("utf-8")

    def _get_userinfo_frm_auth0(self,user):

        endpoint = "https://{}.auth0.com/api/v2/users/{}".format(self.auth0_domain,user)
        headers = {'Authorization': 'Bearer %s' % self.auth0_client_token}
        req = requests.get(endpoint,headers=headers)

        return req.json()

    def _get_user_access_token_with_refresh(self,**kwargs):

        payload = { "client_id": self.auth0_client_id,
                    "client_secret": self.auth0_client_secret,
                    "refresh_token": kwargs["refresh_token"].decode("utf-8"),
                    "grant_type": "refresh_token" }

        _endpt = 'https://{}.auth0.com/oauth/token'.format(self.auth0_domain)
        headers = {'content-type': 'application/json'}

        req = requests.post(_endpt,
                            data=json.dumps(payload),
                            headers=headers)

        return req.json()["access_token"].decode("utf-8")

    def get_user_access_token(self,user):

        # (2) get initial user info
        user_info = self._get_userinfo_frm_auth0(user)

        if str(user_info["identities"][0]["provider"]) == "bitbucket":
            # (3) try to get access token from refresh token
            user_token = self._get_user_access_token_with_refresh(user=user,**user_info["identities"][0])
        else:
            user_token = user_info["identities"][0]["access_token"].decode("utf-8")

        return user_token

Hi @elasticdev,

providing a few assumptions here, because your post dives right into details without giving a high level overview of the setup.

I assume you are using the Bitbucket federation from within the dashboard and have a user already authenticated successfully, is that correct?

Now you want to take the refresh token that’s stored in the Auth0 user profile’s identity (the one retrieved from Bitbucket) in order to retrieve a new Bitbucket access token.

Did I get that right so far?

In any case, the client id and client secret in your refresh token request

curl -X POST -u “client_id:secret” https://bitbucket.org/site/oauth2/access_token -d grant_type=refresh_token -d refresh_token={refresh_token}

should be the ones for Bitbucket, not that of your Auth0 application. So basically the values you’ve put in there (or get from Bitbucket admin section).
In the screenshot below it’s empty cause it’s using the Auth0 dev keys, but for production use, you should’ve put your own key/secret there that you got from Bitbucket.

Hi Mathias!

Thanks for the reply. I apologize. Our application essentially allows sign up through Bitbucket and Github. Then “Auth0” is supposedly automatically allowed permission in the Github/Bitbucket user’s account. This is all automatic. Thus, we don’t have the client/secret.

The big pic:

  • user signs up into our SaaS through Bitbucket credentials

    • User signups with Bitbucket
    • User accepts/authorizes Auth0 in their Bitbucket Account
  • user log ins with Bitbucket credentials

    • the app gets an access token
  • the access token expires

  • the app automatically uses the refresh token to get a new access token without the user logging off and back on.

Issue:

Therefore the application does not have the user’s client/secret for Auth0 authorization to the user’s Bitbucket account. Typically, client/secret are manually done as you have shown in the Bitbucket settings.

Auth0 does not direct me to use the refresh token with the provider Bitbucket’s API directly. Instead Auth0 is shown to proxy refresh token to interact with BitBucket’s API in behave of the app for the Bitbucket user.

It is documented here:

It directs me to use the refresh token with the endpoint on Auth0, which is associated with my domain on Auth0.

_endpt = 'https://{}.auth0.com/oauth/token'.format(self.auth0_domain)

Sorry if I’m a bit confused with things.

Thanks again!

Gary

Does your application make a direct OAuth2 connection to Bitbucket / Github, or are you using Auth0 in between (Auth0 Social Connection > Github & Bitbucket)?

Since you mention

User accepts/authorizes Auth0 in their Bitbucket Account

I believe it must be via Auth0 as the broker. Maybe you can post the code snippet that makes this particular initial authorization/signup request (“Our application essentially allows sign up through Bitbucket and Github.”) - maybe it gets clearer then.

And when you mention that you want to get a new access token, do you mean a new Auth0 access token, or a new Bitbucket access token?

  • If you want to get a new Auth0 access token, you use the Auth0 token endpoint https://{}.auth0.com/oauth/token and the client id / client secret of your Auth0 application (incl. the Auth0 refresh token) that’s registered under Dasboard > Applications

  • If you want to get a new Bitbucket access token, you use the Bitbucket token endpoint https://bitbucket.org/site/oauth2/access_token and use the key/secret of the Bitbucket social connection (with the Bitbucket refresh token that’s in the user’s profile identity for Bitbucket). (You must have gotten some sort of Bitbucket key/secret somewhere in order to make a OAuth2 request to Bitbucket in the first place; relying on the Auth0 dev keys, which aren’t actually visible, wouldn’t work here).

Does your application make a direct OAuth2 connection to Bitbucket / Github, or are you using Auth0 in between (Auth0 Social Connection > Github & Bitbucket)?

Sorry, I probably wasn’t using the correct terminology. Auth0 sits in between as a broker. The vuejs front handles the signup. Then the user gets added to the Auth0 database for my domain. I see this on the bitbucket user setting:

Here is the vuejs code:

/* global Auth0Lock */
export default {
  name: 'signup',
  data() {
    return {
      user: auth.user,
      lock: new Auth0Lock(auth.auth0clientId, auth.auth0Domain, {
        initialScreen: 'login',
        allowLogin: true,
        allowedConnections: ['github', 'bitbucket'],
        container: 'signup-container',
        closable: false,
        avatar: false,
        theme: {
          logo: '',
          primaryColor: '#0071ac',
        },
        language: 'EN',
        languageDictionary: {
          emailInputPlaceholder: 'info@example.com',
          title: 'Authorize with github',
        },
      }),
    };
  },
  mounted() {
    const router = this.$router;

    this.lock.show();
    this.lock.on('authenticated', (authResult) => {
      auth.setAuth0IdToken(authResult.idToken);
      this.lock.getProfile(authResult.idToken, (error, profile) => {
        if (error) {
          console.error(error);
          return;
        }
        // set the token and user profile in local storage
        auth.setAuth0Profile(profile);
        this.user.authenticated = true;
        this.lock.hide();
        router.push('/onboarding');
      });
    });

    this.lock.on('authorization_error', (error) => {
      console.error(error);
      // handle error when authorization fails
    });
  },
  methods: {
  },
};
</script>

<style lang="scss">
  // override hack of auth0 lock
  #signup-container .auth0-lock-header {
    display: none;
  }
</style>

use the key/secret of the Bitbucket social connection (with the Bitbucket refresh token that’s in the user’s profile identity for Bitbucket). (You must have gotten some sort of Bitbucket key/secret somewhere in order to make a OAuth2 request to Bitbucket in the first place

  • Is that when the user first signs up with the frontend? It is not part of the Auth0 response, where I get an access token and a refresh token. That response is shown below:


~

Ok, now it’s getting clearer. So, this is what you need to do:

First, you need to register your own Bitbucket App for Auth0, you cannot rely on the Auth0 Dev Keys.
In Bitbucket, go to Settings > OAuth > OAuth consumers. There you create your application, afterwards you get a key and secret.
As for the Callback URL you register in this OAuth consumer in Bitbucket, it would be the one of your Auth0 tenant: https://YOUR_TENANT.auth0.com/login/callback
(so, not the callback URL of your Vue.js app obviously)

This key/secret, you use in your Bitbucket Social Connection configuration in the Auth0 Dashboard.
Put key and secret in these two input field where it’s marked red, don’t leave it blank / don’t use the Auth0 dev keys.

e934714cb1e39e136e3d6a1954751fe0b4eeae00_2_536x500

Now, the refresh token you got before, and this key/secret you got from bitbucket, you now use in the refresh token request https://bitbucket.org/site/oauth2/access_token

Thanks for the help! I could not have figured out the flow without your help!

I made the changes and documented and coded here if anybody needs an explanation of flow and code snippet:

2 Likes

Thanks a lot for sharing that with the rest of community!

1 Like

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.