Asana / python-asana

Official Python client library for the Asana API v1
MIT License
299 stars 103 forks source link

Is it possible to refresh access token automatically? #108

Closed mark-mishyn closed 4 years ago

mark-mishyn commented 4 years ago

I see that to create asana session there are 2 options: asana.Client.access_token(access_token) and asana.Client.oauth(kwargs)

If I use asana.Client.access_token("...") sometimes I get TokenExpiredError: (token_expired).

I don't wan't to refresh token mannually every time when I need to interact with Asana, so I tried to use asana.Client.oauth(...) with auto_refresh_url and auto_refresh_kwargs, but in this case I've got "NoAuthorizationError: No Authorization: The bearer token has expired...."

Can I handle automatic token refresh to avoid TokenExpiredError?

rossgrambo-zz commented 4 years ago

Hello @mark-mishyn ,

In this situation, I believe you'll be the one implementing the auto-refreshing. Most likely, you will do this on your server when you hit the https://developers.asana.com/docs/#token-exchange . You'll use the refresh token instead of the code.

mark-mishyn commented 4 years ago

So, probably I have to monkey patch asana.Client.request() to something like this?

try:
    response = getattr(self.session, method)(
        url, auth=self.auth, **request_options)
except TokenExpiredError:
    self.session.refresh_token(
                client.session.token_url,
                refresh_token=self.refresh_token,
                client_secret=client.session.client_secret,
                client_id=client.session.client_id)
    response = getattr(self.session, method)(
        url, auth=self.auth, **request_options)
rossgrambo-zz commented 4 years ago

I wrote the response below, but my fault, I thought we were in the node-asana library, so it does not apply here.

I believe a better approach would be something like:

let client = Asana.Client.create({
    clientId: clientId,
    clientSecret: clientSecret,
    redirectUri: 'http://localhost:' + port + '/oauth_callback'
});

client.app.accessTokenFromCode(code).then(function(credentials) {
  res.cookie('token', credentials.access_token, { maxAge: 60 * 60 * 1000, httpOnly: true, secure: true });
  client.useOauth({
    credentials: {
      refresh_token: credentials.refresh_token
    }
  });
});

And do requests normally (This was combined from our readme on refresh_tokens and the webserver example in the /examples directory). This is pseudo code, but I believe this is how the library intended you to re-auth.

In short: I think client.useOauth with credentials containing a refresh token is the way to go.

Again, this comment only applies to node-asana

rossgrambo-zz commented 4 years ago

For python-asana, it appears we don't offer any handling of the refresh_token. so I suppose it's up to you.

I think a better approach would be to create your own "Request" method with the try catch wrapper, and have it call client.request. That way, it's not a change to the library code, and it should be just as reusable.

mark-mishyn commented 4 years ago

Got it. Thank you for your help!

myroslav commented 4 years ago

I managed to refresh token automatically in Python Asana client:

def token_saver(token):
    #some code to persist new token
    pass

def get_client(oauth2_token, oauth2_client_id, oauth2_client_secret):
    return asana.Client.oauth(
        token=oauth2_token,
        auto_refresh_url="https://app.asana.com/-/oauth_token",
        auto_refresh_kwargs={
            "client_id": oauth2_client_id,
            "client_secret": oauth2_client_secret,
        },
        token_updater=token_saver,
    )

It would be great to have auto_refresh_url to be embedded inside AsanaOAuth2Session, but other then that tokens are refreshed with no issues for me.

myroslav commented 4 years ago

Usually my token refreshes fine, but once and only once I got asana.error.NoAuthorizationError error even with oauth session set up as mentioned above. However having retried running my script once more the OAuth2 token got refreshed without any issue. I am suspecting some timing issue with expires_at, but that is just a guess, and not any definite statement.