New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refresh Tokens + Web App Example? #84
Comments
We should definitely have an example of refreshing in a "real world" app. Think it might be good to have a new extended example that includes refreshing as to not overwhelm the reader. As I see it there are (at least) two ways to go about refreshing in a web app. Both happening possibly long after the initial OAuth dance in the middle of normal API interactions.
Note that even if the token has not expired it might have been revoked. This is more likely in 2 than 1 (manifest as a race condition after validation in 1). Unfortunately, there is no telling whether this has happened in terms of a pre-defined OAuth 2 error. One approach would be to "catch" a response code of 401 and use 1 to validate the token and refresh. Naturally this should be covered in a/the example too. @bryanveloso would this help you think? any other bits that need detailing? |
+1 to @bryanveloso, I'm currently debugging why my refresh stuff isn't working and it's not obvious (and the intricacies are not very documented). Google API uses a refresh token, if you need an API to build an example around. The prescribed method, "Define automatic token refresh and update" is supposed to refresh the token automagically, no? If so, I'm not sure what you mean by your questions, @ib-lundgren. @bryanveloso Not sure if this is helpful to you, as I haven't been able to get refreshing working properly myself, but my approach has been to pass a closure into def make_token_updater(token_obj):
def wrapped(new_token):
token_obj.token = new_token
Session.commit()
return wrapped
...
oauth = OAuth2Session(..., token_updater=make_token_updater(current_token)) |
@shazow the refresh is a bit fragile in that the only way requests-oauthlib/oauthlib can tell whether a refresh is needed is whether the time from |
@ib-lundgren So what does the automatic token refresh do? Fwiw, the way Google's oauth2 client handles it is it immediately converts the expires_in time to a unix timestamp and stores that instead of the relative expires_in time. This way it can tell whether it's expired or not at request time. Any reason why we can't do that? |
@shazow That is exactly what I intended to do in the example. It is also what is happening in the automatic token refresh but in a more awkward way than necessary. Currently requests-oauthlib honors the expires_in but what might be better would be an expires_at. I remember starting to look into that but got interrupted and frankly forgot about it since. The pseudo code for the expires in approach would be
token = load_token() We could make that saner by having requests-oauthlib return the expires_at and accept an expires_at timestamp as you suggest. |
Ah, that is a little hacky but makes sense, thanks! +1 on doing it more transparently. :) Seems oauthlib uses datetime objects rather than just epoch int. Dunno why. |
@shazow because I just happened to write it that way. Think unix timestamps would be nicer so can do a little cross library refactor of it. |
Will try and get these examples sorted and add expires_at support by the end of this week. @shazow would it be helpful to send the OAuth2Session into the updater perhaps? and/or the original token? |
Thank you guys, this sounds perfect. 🤘 |
@ib-lundgren Hrm, not sure, I don't see much benefit. I feel like you'll need a closure no matter what you do. Would love to see counterexamples. :) |
@bryanveloso @shazow when/if you have a moment (no rush) please take a look at the w.i.p. refresh example at https://gist.github.com/ib-lundgren/6823954 and let me know if it is useful and what needs work. The doc strings will have more details about use-cases and what we have talked about here. I hope to find time to add an example showing token revocation -> unauthorized API interaction -> token validation -> obtain new token. That could be included either here or in a separate example. Note that with oauthlib master version a new attribute expires_at is added to all tokens (unix timestamp). |
A few thoughts:
Definitely a huge improvement from what we have now. Thanks for this. :) |
Thanks for the feedback :-) The templator is just a temporary thing to save me some time while manually I think I'll remove the "manual" example and the validation bit. Maybe move The expires_at replacement (not subtract) is to force refresh rather than
|
Ah, in that case, I'd fix it to use a real example that people will inevitably copypasta into their code, and mention in a comment how to override it to force a refresh now. The default should be real working code. Don't want getting people confused why their tokens are getting refreshed on every load. :) |
Good point. Will make it a comment.
|
Apologies for being a bother; has any progress been made? |
@bryanveloso If it's any help, I've been using this: import time
from requests_oauthlib import OAuth2Session
def _clean_token(token):
"""Strip out all the stuff we don't need, and add an `expires_at` field."""
return {
'access_token': token['access_token'],
'token_type': token['token_type'],
'refresh_token': token['refresh_token'],
'expires_at': int(time.time() + token['expires_in']),
}
def _token_updater(old_token):
def wrapped(new_token):
token = _clean_token(new_token)
# Do what you need to persist the new token. Save it into the DB or whatever.
old_token.update(token)
Session.commit()
return wrapped
def auth_session(request, token=None, state=None):
if token and 'expires_at' in token:
token['expires_in'] = int(token['expires_at'] - time.time())
return OAuth2Session(
oauth_config['client_id'],
redirect_uri=request.route_url('account_connect'),
scope=oauth_config['scope'],
auto_refresh_url=oauth_config['token_url'],
auto_refresh_kwargs=_dict_view(oauth_config, ['client_id', 'client_secret']),
token_updater=_token_updater(token),
token=token,
state=state,
)
def auth_url(oauth):
return oauth.authorization_url(
oauth_config['auth_url'],
access_type='offline', approval_prompt='force',
)
def auth_token(oauth, response_url):
token = oauth.fetch_token(
oauth_config['token_url'],
authorization_response=response_url,
client_secret=oauth_config['client_secret'],
)
return _clean_token(token) This is using Pyramid's |
This worked great @shazow, thank you. ❤️ |
@bryanveloso Sorry about not following through, been very swamped lately and frankly forgot about this completely. It is back on the todo list now so on the next free slot Ill sort it out :) |
Is there still a need to update the Current oauthlib is setting and checking an |
@akavlie if you store and supply the token that contains expires_at then no it should not be needed. However I've not been looking at this code for a long time now with life keeping my way to busy I can't remember exactly and would need to refresh my memory/experiment a bit. |
I can verify that the |
Cheers. On Mon, Dec 9, 2013 at 10:51 PM, Aaron Kavlie notifications@github.comwrote:
|
Available at http://requests-oauthlib.readthedocs.org/en/latest/examples/real_world_example_with_refresh.html since a little while back. Closing :) |
I've been trying to wrap my head around refresh tokens and the OAuth2 web example (https://requests-oauthlib.readthedocs.org/en/latest/examples/real_world_example.html). Would it be possible to provide an example usage of refresh tokens in that context? Where in the code it would go, etc?
Just a suggestion! 😁
The text was updated successfully, but these errors were encountered: