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

3

4

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?

Uphroe answered 4/1, 2017 at 14:52 Comment(0)
U
9

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.

Uphroe answered 4/1, 2017 at 14:52 Comment(0)
P
11

.get_access_token(strategy) refresh the token automatically if it's expired. You can use it like that:

from social_django.utils import load_strategy
#...
social = request.user.social_auth.get(provider='google-oauth2')
access_token = social.get_access_token(load_strategy())
Paleontography answered 19/5, 2017 at 11:53 Comment(1)
I've updated my answer to reflect this update. thanks!Uphroe
U
9

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.

Uphroe answered 4/1, 2017 at 14:52 Comment(0)
G
2

@NBajanca's update is almost correct for version 1.0.1.

extra_data['expires_in']

is now

extra_data['expires']

So the code is:

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']

I'd also recommend subtracting an arbitrary amount of time from that calc, so that we don't run into a race situation where we've checked the token 0.01s before expiry and then get an error because we sent the request after expiry. I like to add 10 seconds just to be safe, but it's probably overkill:

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

EDIT @NBajanca points out that expires_in is technically correct per the Oauth2 docs. It seems that for some backends, this may work. The code above using expires is what works with provider="google-oauth2" as of v1.0.1

Grandeur answered 15/2, 2017 at 3:11 Comment(5)
I upvoted for the race condition but you are incorrect about expires. expires_in is defined in OAuth 2 RFC and not in this package.Uphroe
PS: I know some backends have this setting "expires_in to expires" but I don't agree. Probably something to be carefull about.Uphroe
Ah, the social object above was for provider='google-oauth2' in my case, which has 'expires' rather than 'expires_in' as of v1.0.1. Will make the edit. Also, I opened an issue for this, but it seems 'auth_time' doesn't automatically change when a token is refreshed: github.com/omab/python-social-auth/issues/1089. Would you expect it to?Grandeur
The provider is actually azuread-oauth2 but you are correct about expires in this package. BTW the correct place to create an issue is hereUphroe
I've updated my answer with the note on race conditions and expires_in. Being only a Recommended response param, not all providers are required to give it and so the approach will always be by provider.Uphroe

© 2022 - 2024 — McMap. All rights reserved.