authlib
authlib copied to clipboard
Refresh and Auto Update Token
I am trying to use signal for automatic token update . As mentioned in the docs we can use signals to do so. Here is my code for the client in Django.
@receiver(token_update)
def on_token_update(sender, token, refresh_token=None, access_token=None,**kwargs):
print('inside token update client views')
if refresh_token:
item = OAuth2Token.find(name=name, refresh_token=refresh_token)
elif access_token:
item = OAuth2Token.find(name=name, access_token=access_token)
else:
return
# update old token
item.access_token = token['access_token']
item.refresh_token = token.get('refresh_token')
item.expires_at = token['expires_at']
item.save()
oauth = OAuth()
oauth.register(
name = '{{ client name }}',
client_id = "{{ client id }}",
client_secret = "{{ client secret }}",
update_token = on_token_update,
access_token_url = "https://127.0.0.1:8000/oauth/token/",
refresh_token_url = "https://127.0.0.1:8000/oauth/token/",
authorize_url = "https://127.0.0.1:8000/oauth/authorize",
redirect_url = "https://127.0.0.2/authorize",
grant_type = "authorization_code",
id_token_signing_alg_values_supported = ['RS256'],
authorize_params= {'access_type': 'offline'},
client_kwargs = {
'scope':'openid profile',
'token_endpoint_auth_method': 'client_secret_basic',
},
jwks = {
{{ key set values }}
},
prompt = "consent"
)
import time
def home(request):
user = request.session.get('user')
print('client user: session',user)
if user:
user = json.dumps(user)
return render(request, 'home.html', context={'user': user})
def login(request):
eyantra = oauth.create_client('eyantra')
redirect_uri = "https://127.0.0.2:8001/authorize"
return eyantra.authorize_redirect(request,redirect_uri)
#return HttpResponse("login")
def auth(request):
print('inside callback view in client')
eyantra = oauth.create_client('eyantra')
token = eyantra.authorize_access_token(request,verify=False,redirect_uri="https://127.0.0.2:8001/authorize",grant_type="authorization_code")
print('token is :',token)
user = eyantra.parse_id_token(request, token)
request.session['user'] = user.get('name')
return redirect('/')
def logout(request):
request.session.pop('user',None)
return redirect('/')
have added the update_token parameter inside oauth register function.Also the refresh_token_url. But token is not being updated after the respective expire_in time. Am I missing out something ?
The sample in the docs simply doesn't work. (I stumbled across this as well.) But you should be able to modify the below to work for you:
from authlib.integrations.django_client.integration import token_update
from django.dispatch import receiver
import pendulum
import logging
logger = logging.getLogger(__name__)
class TokenManager(models.Manager):
def update_token(self, name, token, refresh_token=None, access_token=None):
logger.debug("Auto-update of token(%s) started", name)
# This assumes the unique name as seen on the model below.
item = self.get(name=name)
# update old token
item.update(token)
logger.debug("Update complete.")
class Token(models.Model):
objects = TokenManager()
name = models.CharField(max_length=20, unique=True)
# Oauth tokens:
token_type = models.CharField(max_length=40)
access_token = models.CharField(max_length=5000)
refresh_token = models.CharField(max_length=5000, null=True)
expires_at = PendulumDateTimeField()
refresh_url = models.URLField(null=True)
def __str__(self):
return f"{self.name}: a_t:{self.access_token[0:4]}..., exp:{self.expires_at}"
def update(self, token):
self.access_token = token["access_token"]
self.refresh_token = token.get("refresh_token")
self.expires_at = pendulum.from_timestamp(token["expires_at"])
self.token_type = token["token_type"]
self.save()
@property
def expired(self):
return self.expires_at < pendulum.now()
@property
def expiration_remaining(self):
return self.expires_at - pendulum.now()
def to_token(self):
return dict(
access_token=self.access_token,
token_type=self.token_type,
refresh_token=self.refresh_token,
expires_at=int(self.expires_at.timestamp()),
)
@receiver(token_update)
def update_token(
name, token, refresh_token=None, access_token=None, **kwargs
): # pylint: disable=unused-argument
Token.objects.update_token(name, token, refresh_token, access_token)
Admittedly I do the update in the manager as I prefer as much as possible for code to be on my model and not elsewhere, in case I want to update it later. So my receiver is just one line redirecting to the manager responsible. (And you probably want to use generic datetimefields, not the pendulum-backed ones I use.)
Also be aware, unless you're on authlib 0.14.3 or later, the django integration is broken for refresh: #193 (If you're using the metadata url)
@dragonpaw can you help me to update the documentation?
@dragonpaw can you help me to update the documentation?
Sure, how can I help?
- Has this already been merged in the docs?
- @dragonpaw , by any chance, do you also have a Flask example?
- @dragonpaw , by any chance, do you also have a Flask example?
Sadly, I do not. And with Flask, the implementation would be entirely dependent what database system you went with. The SqlAlchemy implementation would look nothing like what a mongo backend implementation would look like. (The last time I wrote a Flask app, it was Aerospike for the database...)
@aFluxx did you manage to make it work with the Flask App?
@aFluxx I have the same question as @verdan. I am having issues getting this functioning in flask.
In the flask client, Authlib uses current_app as the signal sender, contrary to the documentation which explicit states, "Never pass current_app as sender to a signal." (https://flask.palletsprojects.com/en/2.1.x/signals/?highlight=signals#sending-signals). As explained in the references documentation, Authlib should be using current_app._get_current_object() as the sender instead. The effective result is that Authlib currently does not reliably support signals in the flask client.