Skip to content
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: Refresh token expiration window #2827

Closed

Conversation

bill-robbins-ss
Copy link

@bill-robbins-ss bill-robbins-ss commented Oct 29, 2021

  • Introduce optional configuration value oauth2.refresh_token_rotation.grace_period that can be set as a duration (1m,
  • Add a new migration: boolean used SQL column in hydra_oauth2_refresh

When grace_period has positive duration the act of using a refresh token will not invalidate the token and instead mark it as used in the database and set its expiration time to the UTCNOW + the value of the grace period. This will allow a client to continue to use a refresh token for the duration of the grace period.

Related issue(s)

#1831
Fosite 255

  • I have read the contributing guidelines.
  • I have referenced an issue containing the design document if my change
    introduces a new feature.
  • I am following the
    contributing code guidelines.
  • I have read the security policy.
  • I confirm that this pull request does not address a security
    vulnerability. If this pull request addresses a security. vulnerability, I
    confirm that I got green light (please contact
    security@ory.sh) from the maintainers to push
    the changes.
  • I have added tests that prove my fix is effective or that my feature
    works.
  • I have added or changed the documentation.

Further Comments

This change includes a modification to Fosite in PR 634

Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good :) Could you please also add some docs for this? :)

scripts/create-migration.sh Outdated Show resolved Hide resolved
persistence/sql/persister_oauth2.go Outdated Show resolved Hide resolved
persistence/sql/persister_oauth2.go Outdated Show resolved Hide resolved
persistence/sql/persister_oauth2.go Show resolved Hide resolved
@bill-robbins-ss bill-robbins-ss changed the title Refresh token expiration window feat: Refresh token expiration window Nov 1, 2021
@bill-robbins-ss bill-robbins-ss marked this pull request as ready for review November 1, 2021 20:22
Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, I've got a couple of questions and ideas :)

docs/docs/guides/token-expiration.mdx Outdated Show resolved Hide resolved
docs/docs/guides/token-expiration.mdx Outdated Show resolved Hide resolved
persistence/sql/models/models.go Outdated Show resolved Hide resolved
persistence/sql/persister_oauth2.go Outdated Show resolved Hide resolved
persistence/sql/persister_oauth2.go Outdated Show resolved Hide resolved
@@ -280,13 +325,27 @@ func (p *Persister) deactivateSessionByRequestID(ctx context.Context, id string,
return sqlcon.HandleError(
p.Connection(ctx).
RawQuery(
fmt.Sprintf("UPDATE %s SET active=false WHERE request_id=?", OAuth2RequestSQL{Table: table}.TableName()),
fmt.Sprintf("UPDATE %s SET active=false, used=true WHERE request_id=?", OAuth2RequestSQL{Table: table}.TableName()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need used when we have active already?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, findSessionBySignature will return errors when a session is marked inactive.

persistence/sql/persister_oauth2.go Show resolved Hide resolved
oauth2/fosite_store_helpers.go Outdated Show resolved Hide resolved

if ! used {
session := requester.GetSession()
session.SetExpiresAt(fosite.RefreshToken, time.Now().UTC().Add(gracePeriod))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to understand this a bit better - when refreshing a token we do not deactivate it (active: false) immediately. Instead, if grace is enabled, we extend the expiry time by X from NOW, and we also set used: true. The next time we refresh the token, and we are still in the refresh grace period, nothing is updated.

When we refresh the token again, and NOW + x has passed, fosite will see the key as expired, implying that it is no longer active.

I think this has some serious implications. I am not 100% any more how token reuse detection works, but I think it doesn't trigger on expired tokens, only on inactive ones. Because an expired token has not been reused, it just expired! And since it's expired, you can't use it. A used (active=false) token though has been used, thus, reuse means invalidation of all other tokens.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're saying

  • this will work but the error will be wrong (expired not reused)
  • when reused and expired, since it isn't detected as reused other tokens are not revoked

I mentioned in an earlier comment that setting active=false won't work for grace periods, since calls to findSessionBySignature will not succeed. To properly detect that case (active==true, in_grace_period==true, expired) something could be modified to deal with it, I'm worried that changing findSessionBySignature to throw an expired error may have undesirable repercussions. From what I can tell token validation, which includes expiration checking, is done at a different layer and is part of a Strategy. The persistence layer does not have access to token strategies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was what I was trying to say :)

The best way to prove/disprove this hypothesis is to introduce a test which covers this. If there's no regression on revokation, this should be fine! It would theoretically also be fine to revoke the token chain on expiration also, not just on reuse.

@aeneasr
Copy link
Member

aeneasr commented Jan 11, 2022

@bill-robbins-ss I fixed the conflicts and got parts of the CI to pass again, however there seems to be an linting issue. Would it be possible for you to take a look?

https://app.circleci.com/pipelines/github/ory/hydra/3404/workflows/bd1ecd98-8d78-4ea0-835a-5b45ff9a7aae/jobs/34581

ps: For motivation, we have merged several large-scale changes to Ory Hydra so there's a good chance this will land very quickly as well :)

@bill-robbins-ss
Copy link
Author

@aeneasr this is great, I'll be checking it out this week hopefully getting those rules to pass. Do you know how releasing will be done? We'll want to upgrade our instance to take advantage of the great new things!

@aeneasr
Copy link
Member

aeneasr commented Jan 12, 2022

Once this is merged we'll do a new minor release

@CLAassistant
Copy link

CLAassistant commented Jan 20, 2022

CLA assistant check
All committers have signed the CLA.

@bill-robbins-ss
Copy link
Author

hey @aeneasr I fixed the linting errors that came from me, but he other 3 are on code that I didn't touch. Please advise.

aeneasr added a commit to ory/docs that referenced this pull request Feb 14, 2022
…ation-window

# Conflicts:
#	CONTRIBUTORS
#	docs/docs/guides/token-expiration.mdx
#	docs/docs/reference/configuration.md
Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the work and sorry for the late review time. I did fix some things in the PR but while working on it, I realized that there is still some way to go. In particular:

  1. The new in_grace_period column breaks all tables that do not have this column as they all re-use the same struct to load stuff from the database
  2. The feature is not end-to-end tested
  3. Documentation is missing

Given the sensitivity of this feature we need to ensure that everything works end-to-end. So:

  1. Refreshing a token within the grace period works
  2. Revoking a token revokes also all grace tokens
  3. Rotating a grace token works and does not affect other tokens
  4. Token reuse still works after grace period and revokes all of those tokens also

Let me know if you need any help with this!

@aeneasr aeneasr marked this pull request as draft February 14, 2022 14:31
@bill-robbins-ss
Copy link
Author

Hey @aeneasr thank you for checking in on this.

  1. I can introduce a new struct to handle the new table schema, does that seem reasonable?
  2. I would be delighted to add e2e tests, I think they belong here?
  3. It looks like you added documentation with this change. If there is more that needs to be added just let me know where and I'll add it !

Also, I saw that you removed the SetConfig method - I introduced it for the test refresh token enters grace period when configured. Is that same method available somewhere else ?

@aeneasr
Copy link
Member

aeneasr commented Feb 15, 2022

Thank you @bill-robbins-ss for the fast response and keeping engaged. I have this PR on my prio list now and will be more active reviewing it. You can also ping me on Slack if I am too slow :)

Regarding your questions:

I can introduce a new struct to handle the new table schema, does that seem reasonable?

It does! I would encourage you though to maybe rebase this PR right away with #2796 to avoid any big conflicts during merge.

I would be delighted to add e2e tests, I think they belong here?

Exactly, I would suggest to cover the following use cases:

  1. Refreshing a refresh token without grace period (I believe we have this already)
  2. Refreshing a refresh token without grace period that is expired (I believe we have this already)
  3. Refreshing a refresh token with grace period
  4. Refreshing a refresh token with grace period that is expired
  5. Refreshing a refresh token 1 (before expiry) 2 (after expiry) 3 (after expiry) with grace period

We also need to cover revokation and deleting the consent:

  1. Revoke a refresh token that was granted before expiry
  2. Revoke consent of refresh token that was granted before expiry
  3. Revoke a refresh token that was granted before expiry with grace period
  4. Revoke consent of refresh token that was granted before expiry with grace period
  5. Revoke a refresh token that was granted before expiry with grace period that was expired
  6. Revoke consent of refresh token that was granted before expiry with grace period that was expired
  7. Revoke a 1 (before expiry) 2 (after expiry) 3 (after expiry) refresh token grace period or consent
  8. Revoke consent of 1 (before expiry) 2 (after expiry) 3 (after expiry) refresh token grace period or consent
  9. For the token revokation and consent deletion we need to check whether all tokens have been revoked. It should not be possible to revoke just one refresh token "chain".

Checking whether we can refresh graceful tokens is important too:

  1. Refresh a 1 (before expiry) 2 (after expiry) 3 (after expiry) with grace period or consent that was granted normally with grace period. We should refresh at least 2 or 3 times to ensure that the "refresh token tree" works correctly.

As well as introspection:

  1. Refreshing a 1 (before expiry) 2 (after expiry) 3 (after expiry) times with grace period and introspecting their access tokens

I believe most functions are there already to test this, so it should not be too much work hopefully.

Besides e2e tests, it probably also makes sense to start with some Go test first, as that will make debugging so much easier for you!

t.Run("case=perform authorize code flow with ID token and refresh tokens", func(t *testing.T) {

It looks like you added documentation with this change. If there is more that needs to be added just let me know where and I'll add it !

Apologies, I got in review mode and am not fully used to the new docs system yet 😓

Also, I saw that you removed the SetConfig method - I introduced it for the test refresh token enters grace period when configured. Is that same method available somewhere else ?

Yes, it's now defined on the interface level so no reflection is needed!

aeneasr added a commit to ory/docs that referenced this pull request Feb 23, 2022
@aeneasr
Copy link
Member

aeneasr commented Apr 11, 2022

@bill-robbins-ss please let me know when you think that this is good for another review :)

@yuekun0707
Copy link

Hi, any update here? Is configuration described here https://www.ory.sh/docs/hydra/guides/token-expiration#refresh-token-rotation ready for using?

@aeneasr
Copy link
Member

aeneasr commented Jul 26, 2022

Looks like that made it to prod on mistake, the feature is not yet usable!

aeneasr added a commit to ory/docs that referenced this pull request Jul 26, 2022
@yuekun0707
Copy link

@aeneasr thank you for your reply! I really need feature like this. We use hydra in our project and it works well. But now we want to connect to google assistant by account linking. There is a problem with the refresh token rotation. The error from google like this:
"Refresh token has been rotated. This is not forbidden, however we do not see much benefit that rotating the refresh token can provide but the potential problem it has. We also tried to refresh token with the old refresh token after it has been rotated. Refresh didn't work, this means partner invalidated the old refresh token right after the rotation. Partners shall only invalidate the old refresh token after seeing we use the new one to ensure we got it successfully."
So, is there any way I can try for this?

@misl6
Copy link

misl6 commented Nov 29, 2022

Hi @aeneasr and @bill-robbins-ss !
As other users pointed out, this is pretty much a requirement to pass the Google Assistant Actions certification.

Is this feature something scheduled and that we may expect in a near future?
There's anything we can do on our side (as users) to help you to make that happen?

@StarAurryon
Copy link
Contributor

Hi @aeneasr, I am willing to continue the work on the coming months as we are currently experiencing network token loss when Windows goes to sleep with our app and also with our android / ios app.

Did @bill-robbins-ss signed the Ory Code Agreement so that I can reuse his code and pursue the implementation ?

Kind regards,

@shipstation shipstation closed this by deleting the head repository May 18, 2024
@joshuaflanagan
Copy link

The issue was closed because the original fork was mistakenly deleted. I created a new fork and pushed the branch, but it would not let me push to the same branch name, so it is now at https://github.com/shipstation/hydra/tree/refresh-token-expiration-window-2. Unfortunately, there does not appear to be a way to change the branch for this PR to reference the new branch. Either way, it looks like the code is still accessible in this PR - maybe an ory/hydra project owner can re-open the PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants