Decide When to Update OAUTH2 Token Using Python Social Auth

I think this is mainly a question about best practices.

I have an OAUTH2 provider that issues access tokens (valid for 10 hours) as long as the tokens are updated.

I found here that it is fairly easy to update an access token, but I cannot figure out how to decide when it is time for an update.

The easy answer is probably โ€œwhen it no longer worksโ€, which means when I get HTTP 401 from the backend. The problem with this solution is that it is not so efficient, plus I can only assume that I received 401 because the token has expired.

In my django application, I found that user social auth has an Extra data field containing something like this:

{ "scope": "read write", "expires": 36000, "refresh_token": "xxxxxxxxxxxxx", "access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "token_type": "Bearer" }

but I'm not sure how to use the expires field.

So my question is: how do I know if the access token has expired and I need to update it?

EDIT : I just found this comment , which seems relevant, but I canโ€™t figure out how to enable this new function in the pipeline in order to work while updating the token.

+8
python django python-social-auth
source share
2 answers

I finally figured it out. The reason I get confused at first is because there are actually two cases:

  • When the user comes from the entrance, therefore, when the pipeline is mostly executed.
  • When the token is updated by calling refresh_token social auth refresh_token

To solve the first case

I created a new function for the pipeline:

 def set_last_update(details, *args, **kwargs): # pylint: disable=unused-argument """ Pipeline function to add extra information about when the social auth profile has been updated. Args: details (dict): dictionary of informations about the user Returns: dict: updated details dictionary """ details['updated_at'] = datetime.utcnow().timestamp() return details 

in the settings I added it to the pipeline right before load_extra_data

 SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', # the following custom pipeline func goes before load_extra_data 'backends.pipeline_api.set_last_update', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', 'backends.pipeline_api.update_profile_from_edx', 'backends.pipeline_api.update_from_linkedin', ) 

and, still in the settings, I added a new field to the additional data.

 SOCIAL_AUTH_EDXORG_EXTRA_DATA = ['updated_at'] 

For the second case:

I refresh_token method of my backend to add an extra field.

 def refresh_token(self, token, *args, **kwargs): """ Overridden method to add extra info during refresh token. Args: token (str): valid refresh token Returns: dict of information about the user """ response = super(EdxOrgOAuth2, self).refresh_token(token, *args, **kwargs) response['updated_at'] = datetime.utcnow().timestamp() return response 

Still in the base class, I added an extra field to retrieve the expires_in field coming from the server.

 EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('expires_in', 'expires_in'), ('token_type', 'token_type', True), ('scope', 'scope'), ] 

At this point, I have a timestamp when the access token was created ( updated_at ) and the number of seconds it will be valid ( expires_in ).

NOTE. updated_at is approximate since it is created on the client, and not on the provider server.

Now the only thing missing is a function to check if it is time to update the access token.

 def _send_refresh_request(user_social): """ Private function that refresh an user access token """ strategy = load_strategy() try: user_social.refresh_token(strategy) except HTTPError as exc: if exc.response.status_code in (400, 401,): raise InvalidCredentialStored( message='Received a {} status code from the OAUTH server'.format( exc.response.status_code), http_status_code=exc.response.status_code ) raise def refresh_user_token(user_social): """ Utility function to refresh the access token if is (almost) expired Args: user_social (UserSocialAuth): a user social auth instance """ try: last_update = datetime.fromtimestamp(user_social.extra_data.get('updated_at')) expires_in = timedelta(seconds=user_social.extra_data.get('expires_in')) except TypeError: _send_refresh_request(user_social) return # small error margin of 5 minutes to be safe error_margin = timedelta(minutes=5) if datetime.utcnow() - last_update >= expires_in - error_margin: _send_refresh_request(user_social) 

I hope this can be useful for other people.

+3
source share

Currently, the extra_data field now has auth_time . You can use it together with expires to determine if access_token correct:

 if (social.extra_data['auth_time'] + social.extra_data['expires'] - 10) <= int(time.time()): from social_django.utils import load_strategy strategy = load_strategy() social.refresh_token(strategy) 

An extra "10" second is present there to prevent the race condition when the access_token expires before the next code is executed.

This question gives detailed information: How to update the token using social-auth-app-django?

+1
source share

All Articles