Search code examples
pythondjangopython-social-auth

How can I refresh the token with social-auth-app-django?


I use Python Social Auth - Django to log in my users.

My backend is Microsoft, so I can use Microsoft Graph but I don't think that it is relevant.

Python Social Auth deals with authentication but now I want to call the API and for that, I need a valid access token. Following the use cases I can get to this:

social = request.user.social_auth.get(provider='azuread-oauth2')
response = self.get_json('https://graph.microsoft.com/v1.0/me',
                         headers={'Authorization': social.extra_data['token_type'] + ' ' 
                                                   + social.extra_data['access_token']})

But the access token is only valid for 3600 seconds and so I need to refresh, I guess I can do it manually but there must be a better solution. How can I get an access_token refreshed?


Solution

  • Using load_strategy() at social.apps.django_app.utils:

    social = request.user.social_auth.get(provider='azuread-oauth2')
    strategy = load_strategy()
    social.refresh_token(strategy)
    

    Now the updated access_token can be retrieved from social.extra_data['access_token'].

    The best approach is probably to check if it needs to be updated (customized for AzureAD Oauth2):

    def get_azuread_oauth2_token(user):
        social = user.social_auth.get(provider='azuread-oauth2')
        if social.extra_data['expires_on'] <= int(time.time()):
            strategy = load_strategy()
            social.refresh_token(strategy)
        return social.extra_data['access_token']
    

    This is based on the method get_auth_tokenfrom AzureADOAuth2. I don't think this method is accessible outside the pipeline, please answer this question if there is any way to do it.

    Updates

    Update 1 - 20/01/2017

    Following an Issue to request an extra data parameter with the time of the access token refresh, it is now possible to check if the access_token needs to be updated in every backend.

    In future versions (>0.2.1 for the social-auth-core) there will be a new field in extra data:

    'auth_time': int(time.time())
    

    And so this works:

    def get_token(user, provider):
        social = user.social_auth.get(provider=provider)
        if (social.extra_data['auth_time'] + social.extra_data['expires']) <= int(time.time()):
            strategy = load_strategy()
            social.refresh_token(strategy)
        return social.extra_data['access_token']
    

    Note: According to OAuth 2 RFC all responses should (it's a RECOMMENDED param) provide an expires_in but for most backends (including the azuread-oauth2) this value is being saved as expires. Be careful to understand how your backend behaves! An Issue on this exists and I will be update the answer with the relevant info when it exists.

    Update 2 - 17/02/17

    Additionally, there is a method in UserMixin called access_token_expired (code) that can be used to assert if the token is valid or not (note: this method doesn't work for race conditions, as pointed out in this anwser by @SCasey).

    Update 3 - 31/05/17

    In Python Social Auth - Core v1.3.0 get_access_token(self, strategy) was introduced in storage.py.

    So now:

    from social_django.utils import load_strategy
    
    social = request.user.social_auth.get(provider='azuread-oauth2')
    response = self.get_json('https://graph.microsoft.com/v1.0/me',
                         headers={'Authorization': '%s %s' % (social.extra_data['token_type'], 
                                                              social.get_access_token(load_strategy())}
    

    Thanks @damio for pointing it out.