-
Notifications
You must be signed in to change notification settings - Fork 366
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
feat: Implement OAuthenticator.refresh_user
#579
Conversation
I'll be super excited to try get this to eventually land :) |
@yuvipanda I don't have bandwidth to work on it anymore, so feel free to take over the code ! |
@Wykiki I'll poke around and try to find someone! |
And thank you for your contribution :) |
Hello! Was any progress made here? This would be useful for my work, and this looks like a fairly complete implementation (thanks!). |
I've been using this with gitlab with a couple of compatibility changes (adding the redirect_url and the client info) and it seems to work. Are there any other particular changes that would need to be done here for this to be merged? |
for more information, see https://pre-commit.ci
a585f66
to
61f1b03
Compare
for more information, see https://pre-commit.ci
refresh_user should always refresh auth, JupyterHub config already exists to determine expiration
I finally have time to look this over, and I think there is some simplification we can do. In particular, I think we should rely on JupyterHub's own I also think Another affected case where we need to make a choice: oauth without refresh tokens. In the absence of refresh_tokens, should refresh_user:
|
- do not refresh if auth_state is disabled (would force re-login every 5 minutes in default config) - always refresh if refresh_token is defined - if refresh_token not available, only check validity of access_token and refresh associated user info
This now implements
I think the one problem with this always-refresh design is that using a refresh_token often invalidates existing access tokens (GitHub does this, for. This is a problem for the case:
This is very likely to happen, as the activity notifications coming from the user server will eventually trigger As a result, I think preemptively refreshing the access token is not something we should do, even though I really want to do it on spawn start. Unfortunately, JupyterHub doesn't provide Authenticators the info they need to refresh auth on spawn, but not the rest of the time. Plus, it would be a problem for named servers, where multiple spawners may be concurrent - forcefully refreshing the token on every start would actually mean only the latest spawn has a valid token. |
This now only refreshes access tokens after they expire. The flow:
There is still no need to check an expiration date, because we need to attempt to use the token to refresh anyway, and we can assume failure means expiration is likely ( |
only consider HTTP 4XX expired auth
I've tested this with the mock-provider example and it's working as expected. I think it's ready to go, but I could also explore adding a fixture for running the mock-oauth2-provider if folks want deeper integration testing (this PR exercises our code just fine, but assumes the mocks are accurate). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking this up @minrk and for explaining the reasoning behind simplifying the logic and why this doesn't preemptively refreshes the access token.
I think get_prev_refresh_token
needs to be removed now as it not used and confusing. Other than this, the PR looks ready to merge to me. I think creating a fixture for running the mock-oauth2-provider should be tracked by another issue and PR as a more general improvement to the testing infra.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring for build_auth_state_dict
isn't quite correct anymore, it still says it's (only) called by authenticate
:
oauthenticator/oauthenticator/oauth2.py
Line 1153 in 90da9d7
Called by the :meth:`oauthenticator.OAuthenticator.authenticate` |
If it's easier you could take out the cross reference and similarly for other docstrings. It's helpful for understanding the code, but it's a pain to keep in sync.
- remove incorrect 'the' - add `refresh_user` where appropriate - fix some links to the current class
Thanks for the review! I think I've addressed it. I removed the unused method, updated the docstrings, and updated all the method cross-links so they resolve properly. See here for the rendered refresh_user docstring. I think the last question is if this should be enabled by default, and I think the answer is yes. You can opt-out by setting: c.Authenticator.auth_refresh_age = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we treat the removal of get_prev_refresh_token
as breaking, or an internal implementation detail?
I don't think it should be considered breaking, but I can put it back as deprecated if folks want. |
oauthenticator.OAuthenticator.refresh_user
methodOAuthenticator.refresh_user
I'm very keen on this PR and it looks like it's ready to be merged? (Hopeful smile) |
Going ahead with merge since this is approved. Thanks @Wykiki for getting it started and everyone for their patience! |
This MR aims to implement the
refresh_user()
'sAuthenticator
method inOAuthenticator
class.Following #398 comment.
At the time of writing, nothing has been tested, and provider specific code would be needed, as the OAuth2 spec does not force the
expires_in
field in theaccess_token
response body.I may not have bandwidth soon to work again on this, feel free to take the lead !
closes #475
closes #490
closes #398