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

Fix: Use proper scopes in refresh token handler #698

Closed

Conversation

silverspace
Copy link

Fixes #696

The token refresh handler currently has two issues related to scopes:

  1. It does not read or honor the optional scope form parameter
  2. It defaults to the current client scopes, rather than the originally granted scopes. One issue related to this is that if the client scopes are narrowed, then the existing refresh tokens may fail to refresh, even if the user originally consented to those scopes.

This PR fixes the OAuth2 token refresh handler to:

  • Read and use the optional 'scope' form parameter, if present.
  • Otherwise default to requesting the originally granted scopes.

The token refresh handler endpoint should be completely agnostic of:

  • The originally requested scopes
  • The client scopes (both current and past client scopes)

Related Issue or Design Document

#696

Checklist

  • I have read the contributing guidelines and signed the CLA.
  • I have referenced an issue containing the design document if my change introduces a new feature.
  • 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 [email protected]) 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 necessary documentation within the code base (if appropriate).

Further comments

@silverspace silverspace changed the title Use proper scopes in refresh token handler Fix: Use proper scopes in refresh token handler Aug 31, 2022
Fixing the OAuth2 token refresh handler to:
 - Read and use the optional 'scope' form parameter, if present.
 - Otherwise default to requesting the originally granted scopes.

This endpoint should be completely agnostic of:
 - The originally **requested** scopes
 - The **client scopes** (both current and past client scopes)

Fixes ory#696
Updating our OAuth2 token refresh handler tests to completely ignore
the **Client Scopes** and **Originally Requested Scopes**. Instead,
the originally granted scopes should be the only scopes validated
against.

Also adding some tests to validate the optional 'scope' parameter,
as outlined in https://www.rfc-editor.org/rfc/rfc6749#section-6

Note that this implementation returns an ErrInvalidScope if the
'scope' form parameter is defined but empty.
@silverspace silverspace force-pushed the greg/custom_refresh_token_scope_fix branch from cfcbd40 to d9ecd46 Compare August 31, 2022 23:50
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 very much for the detailed issue and PR! I think this is working though as intended for a reason, please check my comment. Thanks!

request.SetRequestedAudience(originalRequest.GetRequestedAudience())

for _, scope := range originalRequest.GetGrantedScopes() {
if !c.Config.GetScopeStrategy(ctx)(request.GetClient().GetScopes(), scope) {
Copy link
Member

Choose a reason for hiding this comment

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

For context: This design decision was originally made to cover the use case where a client could gain access to scopes that are not in its allowed list. This can happen when a client is updated. I'd say this behavior is correct. The client should not receive scopes which are not in its allowed list.

Copy link
Contributor

Choose a reason for hiding this comment

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

This check was merely moved here from what I can tell: https://github.com/ory/fosite/pull/698/files#diff-e733adcae5e1fe83d4192fc112318cfd1d77b5ad76b6d71d383ae705ea29e3f2R111-R112

From looking purely at the code change what happens is if the request to the token endpoint is a refresh and the form contains a scopes key it uses that as the source of the intended scopes, if it doesn't it uses the original request as a source of the intended scopes, then performs the original check to ensure the client is allowed to issue those scopes.

I can see another issue with this PR though. I'll make a review.

Copy link
Contributor

Choose a reason for hiding this comment

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

It is strange that the conformity test failed though...

Copy link
Author

@silverspace silverspace Sep 1, 2022

Choose a reason for hiding this comment

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

For context: This design decision was originally made to cover the use case where a client could gain access to scopes that are not in its allowed list. This can happen when a client is updated. I'd say this behavior is correct. The client should not receive scopes which are not in its allowed list.

@aeneasr Please let me know if James' response makes sense, or if you still have issues you'd like to chat through with this implementation.

Copy link
Contributor

@james-d-elliott james-d-elliott Sep 1, 2022

Choose a reason for hiding this comment

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

I see the issue, we should probably still check the intended scopes against the client to ensure it absolutely can issue them (even though it theoretically did before). But lets see what Aeneasr says.

Copy link
Author

@silverspace silverspace Sep 8, 2022

Choose a reason for hiding this comment

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

Hi @aeneasr , the above does NOT follow RFC6749. This is precisely the bug that I am encountering in Fosite, and the reason I filed this PR to fix it. The implementation according to the RFC should be:


  1. Authorization Grant is done with scope a, b, c - client is granted scopes a, b, c
  2. Refresh grant is executed without ?scope parameter, thus scope a, b, c is granted
  3. Refresh grant is executed again with ?scope=a,b, and scope a,b is granted
  4. Client is updated to only allow scope a, b
  5. Refresh grant is executed without ?scope parameter, scope a, b, c is granted
  6. Refresh grant is executed with ?scope=a,b,c parameter, refresh fails succeeds
  7. Refresh grant is executed with ?scope=a,b parameter, and scope a,b is granted

Refreshing an access token should be completely agnostic to the currently configured client scopes. Note that the entire RFC makes no mention of the term client scope.

Copy link
Author

Choose a reason for hiding this comment

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

The other issue that this PR attempts to resolve is that steps (3) and (7) are broken in Fosite, since Fosite does not read the ?scope=a,b parameter.

Copy link
Member

Choose a reason for hiding this comment

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

I respectfully disagree. #698 (comment) is not what I trust the correct sequence to be, I think it reduces security, and it's also not how I interpret the RFC. What could work to convince me otherwise is to get the conformance tests (which test OIDC conformity ...) to pass since they are failing on this PR (potentially due to the changes made), and to get someone from the OAuth2 IETF authors or maintainers to agree with your interpretation.

Copy link
Author

Choose a reason for hiding this comment

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

Hi @aeneasr I can understand your reservations, and desire to get sign-off from the OAuth2 IETF authors. How about we focus on the more clear-cut issue of Fosite not honoring the ?scope=a,b parameter for now? That would at least unblock my particular use case.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, Fosite should definitely respect that parameter! :)

@silverspace
Copy link
Author

Perhaps we can put aside the "client scope" discussion for a moment, and simply agree that the Fosite library DOES have a bug whereby the scope form parameter is completely ignored. The refresh token handler does not read the scope form parameter in the current implementation, so it is currently impossible to request a more narrowly scoped access token.

@james-d-elliott
Copy link
Contributor

james-d-elliott commented Sep 3, 2022

Perhaps we can put aside the "client scope" discussion for a moment, and simply agree that the Fosite library DOES have a bug whereby the scope form parameter is completely ignored. The refresh token handler does not read the scope form parameter in the current implementation, so it is currently impossible to request a more narrowly scoped access token.

Yeah I agree with focusing on the one aspect, it's more efficient. Especially with focusing on something I think is less ambiguous/open to interpretation/not explicitly stated in the spec, is a good idea.

@silverspace
Copy link
Author

@aeneasr please let me know if you have time to discuss #698 (comment)

@aeneasr
Copy link
Member

aeneasr commented Nov 1, 2022

Given the discussion and reasoning above I will close this PR. Any ideas for future work outlined in this PR we‘ll happily accept though :)

@aeneasr aeneasr closed this Nov 1, 2022
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.

Unable to request narrower scopes in OAuth2 Refresh Token Exchange
3 participants