Hey there,
Have you considered adding a feature for refreshing access tokens with a given refresh token? Or do you think it's outside the scope of allauth?
Cheers,
Padraic
That's in scope, though I think it is best to integrate https://github.com/requests/requests-oauthlib to do the actual dirty work...
Cheers for the reply! Yeah, that'd work, was looking at it for the same. I'd be happy to try to work on a pull request if you'd have any suggestions on things to be careful of?
Hey is there any more thought on this? I've been trying to auth against Google, but refresh tokens aren't saving into the token_secret.
For me it's saving the Google refresh_token in the 'token_secret' field. However, the 'expires_at' seems to work just as a reference, and nothing is done with it. So I am thinking on refreshing the token right after it fails when trying to work with it. For this case I'd need to implement a signal-like method to refresh the token, updating it in SocialToken model and try request again.
However, would that fresh new token get overwritten on a second login?
What would be the best way to handle this?
I hacked together a function to acquire a new access token from the refresh token. It would need some work before it was ready for production, so I'm going to leave it here as a possible starting point for a full implementation or for someone who just wants to get the job done, hack or no hack!
def _refresh_authorise(user, sat):
old_access_token = sat.token
refresh_token = sat.token_secret
app = sat.app
client_id = app.client_id
api_key = app.secret
data={
"grant_type": "refresh_token",
"refresh_token": refresh_token}
auth = requests.auth.HTTPBasicAuth(
client_id,
api_key)
url = "%s/token" % "https://login.eveonline.com/oauth"
params = None
resp = requests.request(
'post',
url,
params=params,
data=data,
headers=None,
auth=auth)
if resp.status_code != 200:
raise Exception("Unexpected code: %d" % res.status_code)
res = resp.json()
access_token = res['access_token']
expires_in = res['expires_in']
sat.token = access_token
sat.expires_at = timezone.now() + timedelta(seconds=expires_in)
sat.save()
return
I think there's a couple different ways to do this, and they each handle the cases a little differently. There's two main things to determine whether allauth should support: known expired tokens (expires_at in SocialApplicationToken), and unknown expired tokens (when you attempt to make a query, and the token fails, thus you need to refresh it).
If we only need to support the known expired tokens, then we should just create a .ensure_valid_token() function on OAuth2Adapter instances. The standard implementation would probably look something like:
def ensure_valid_token(token, force=False):
if token.expires_at > timezone.now() and not force:
return # Already valid, no need to do anything.
# Otherwise, we need to refresh
client = # Construct a valid OAuth2Client using a new `refresh_token_url` field on OAuth2Adapter
token.token = client.get_refresh_token(token.token_secret)
token.save()
Otherwise, if we need to handle errors on the fly, we would have to be able to construct requests-oauthlib.OAuth2Session objects and use the token_updater callback to update with the latest token as you go:
def get_oauth2_session(self, social_application_token):
def token_updater(token):
social_application_token.token = token
social_application_token.save()
extra = {
'client_id': client_id,
'client_secret': client_secret,
}
return OAuth2Session(client_id,
token={'token': social_application_token.token,
'token_type': 'Bearer',
'refresh_token': social_application_token.token_secret},
auto_refresh_kwargs=extra,
auto_refresh_url=self.refresh_url,
token_updater=token_updater)
You can see some more details in the requests-oauth docs
@pennersr Any thoughts?
The second form (handling errors on the fly) is the more robust one -- in edge case situations you may still bump into expired tokens when only checking against expires_at.
As I've implemented this just now for our project and there are details that are different from the code above (e.g. expires_in must be set on the token), here is the code that works for us (in our case using Azure AD):
def get_oauth2_session(request):
""" Create OAuth2 session which autoupdates the access token if it has expired """
# This needs to be amended to whatever your refresh_token_url is.
refresh_token_url = AzureOAuth2Adapter.refresh_token_url
social_token = SocialToken.objects.get(account__user=request.user)
def token_updater(token):
social_token.token = token['access_token']
social_token.token_secret = token['refresh_token']
social_token.expires_at = timezone.now() + timedelta(seconds=int(token['expires_in']))
social_token.save()
client_id = social_token.app.client_id
client_secret = social_token.app.secret
extra = {
'client_id': client_id,
'client_secret': client_secret
}
expires_in = (social_token.expires_at - timezone.now()).total_seconds()
token = {
'access_token': social_token.token,
'refresh_token': social_token.token_secret,
'token_type': 'Bearer',
'expires_in': expires_in # Important otherwise the token update doesn't get triggered.
}
return OAuth2Session(client_id, token=token, auto_refresh_kwargs=extra,
auto_refresh_url=refresh_token_url, token_updater=token_updater)
Hi,
I similar issue on my project and @duebbert solution was very helpful. ( Thank you )
For quite some time I was considering whether to make a contribution,
Is this issue still relevant ?
If yes, I would like to try it.
Most helpful comment
As I've implemented this just now for our project and there are details that are different from the code above (e.g.
expires_inmust be set on the token), here is the code that works for us (in our case using Azure AD):