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 CORS by passing 'Origin' header to OAuthLib #1229

Merged
merged 12 commits into from
Oct 19, 2023

Conversation

akanstantsinau
Copy link
Contributor

@akanstantsinau akanstantsinau commented Dec 11, 2022

Fixes #

Description of the Change

Pass DJango HTTP_ORIGIN header to underlying OAuthLib

It is possible to control CORS by overriding is_origin_allowed method of RequestValidator class.
OAuthLib allows origin if:

  • RequestValidator.is_origin_allowed returns True for particular request
  • Request connection is secure
  • Request has Origin header

Checklist

  • PR only contains one change (considered splitting up PR)
  • unit-test added
  • documentation updated. No documentation. This is the fix for the feature documented in OAuthLib
  • CHANGELOG.md updated (only for user relevant changes) - change is not user relevant
  • author name in AUTHORS

Copy link
Member

@n2ygk n2ygk left a comment

Choose a reason for hiding this comment

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

Can you explain the use case for this given apps using Django CORS settings have been using this library successfully without it?

@akanstantsinau
Copy link
Contributor Author

Using this PR it is possible to enable CORS for OAuth /o/token endpoint only without requirement to setup/configure Django CORS middleware.

Django CORS settings is an alternative way.

Use Case 1:

  • Application does not use Django CORS module (as there are no any third-party web clients)
  • Application needs to setup OIDC provider to let users use another service.
    Solution:
  • Create subclass of class OAuth2Validator
  • Override method is_origin_allowed, e.g.:
def is_origin_allowed(client_id, origin, request, *args, **kwargs):
    # May be do some verification of client_id, origin etc.
    # We may need to check here if client uses PKCE and allow CORS only if it does.
    return true
  • Specify newly created class in OAUTH2_PROVIDER.OAUTH2_VALIDATOR_CLASS setting.

@codecov
Copy link

codecov bot commented Feb 16, 2023

Codecov Report

Merging #1229 (afdfa45) into master (efdf897) will increase coverage by 0.02%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master    #1229      +/-   ##
==========================================
+ Coverage   97.46%   97.48%   +0.02%     
==========================================
  Files          32       32              
  Lines        2092     2111      +19     
==========================================
+ Hits         2039     2058      +19     
  Misses         53       53              
Files Coverage Δ
oauth2_provider/models.py 98.64% <100.00%> (+<0.01%) ⬆️
oauth2_provider/settings.py 100.00% <ø> (ø)
oauth2_provider/validators.py 100.00% <100.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@dopry
Copy link
Contributor

dopry commented May 20, 2023

@akanstantsinau Are you unable to meet your CORS needs using the CORS middleware? The docs recommend using cors-headers for this purpose. I'm wary of adding CORS handling. The scope of oauth support is pretty broad and there is already a lot of surface area in oauth_toolkit. I wouldn't want to add things we didn't have to and that weren't explicitly defined in OAuth/OIDC specifications. I'm willing to hear more about your case for adding this, but convenience isn't going to be a convincing argument.

@akanstantsinau
Copy link
Contributor Author

@dopry Thank your for looking into this PR
I can meet the same functionality using CORS middleware with a bit more coding.
The use case is pretty typical:

  • We have a Django website with some settings in CORS middleware
  • We want to share user details with another service via OIDC (lets refer hose third party service as https://example.com)
  • OIDC client on example.com is web-based, so it requires CORS headers to be set on our /o/token endpoint which is provided by Django OAuth Toolkit
  • We want to allow origin https://example.com only for url /o/token

The solution would be trivial if we do not have existing CORS middelware configs.

CORS_ALLOWED_ORIGINS=["https://example.com"]
CORS_URLS_REGEX = r"^/o/token$"

In our case we already have set CORS_ALLOWED_ORIGINS for some origins for all urls. So to allow https://example.com for /o/token we may implement own handler and attach it to corsheaders.signals.check_request_enabled Django signal.
No issues with it, if you recommend this approach.

The only motivation for this PR was to reuse already existing CORS feature in oauthlib project. It does exactly what we expect - set CORS headers only on /o/token url.
But I see you concerns regarding the scope of support.

Copy link
Contributor

@dopry dopry left a comment

Choose a reason for hiding this comment

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

I think this is a good start to improving our CORS support.

The default OauthLib request validator always returns false, https://github.com/oauthlib/oauthlib/blob/d4b6699f8ccb608152b764919e0bd3d38a7b171f/oauthlib/oauth2/rfc6749/request_validator.py#L656

If we're going to enable this upstream with OAuthLib we also need to

  • implement of is_origin_allowed in oauth2_provider.oauth2_validators.OAuth2Validator.
  • add a field for the allowed origins on the application model
  • ensure all views which should implement CORS in DOT are using the validator

oauth2_provider/oauth2_backends.py Outdated Show resolved Hide resolved
@dopry dopry mentioned this pull request Jun 16, 2023
@dopry
Copy link
Contributor

dopry commented Sep 26, 2023

@akanstantsinau do you have the bandwidth to continue working on this issue?

@akanstantsinau
Copy link
Contributor Author

@dopry Apologize for long delay. Yes I do have bandwidth now to continue work on this.
Your changes requested make sense for me. Will come back soon with updates.

@rcoup
Copy link

rcoup commented Sep 27, 2023

FWIW, we implemeted this, and the approach is working well: It allows us to customise is_origin_allowed() to only be enabled for specific clients for specific origins.

@akanstantsinau
Copy link
Contributor Author

@dopry

  • implemented is_origin_allowed in oauth2_provider.oauth2_validators.OAuth2Validator.
  • added allowed_origins field for the allowed origins on the application model
  • only token endpoint uses is_origin_allowed according to oauthlib implementation. Added tests for grant and refresh token
  • OIDC public endpoints (oidc-connect-discovery-info, jwks-info) use Access-Control-Allow-Origin:*
  • OIDC user-info ignores is_origin_allowed

Copy link
Contributor

@dopry dopry left a comment

Choose a reason for hiding this comment

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

Awesome progress. This is great work and we're super close. I have a few small asks. Mostly simple renaming and docs, otherwise, from a code review perspective, we're pretty much there. After we can get those changes in I'll setup a test env and put it through it's paces!

oauth2_provider/models.py Outdated Show resolved Hide resolved
oauth2_provider/oauth2_validators.py Outdated Show resolved Hide resolved
tests/test_cors.py Outdated Show resolved Hide resolved
tests/test_cors.py Outdated Show resolved Hide resolved
@dopry
Copy link
Contributor

dopry commented Oct 4, 2023

@akanstantsinau This looks great. Can you look at the coverage issue in Codecov? It looks like your tests should be covering those cases. Can you look into it and confirm those cases are covered? Also double check the doc changes to make sure there isn't a typo that is throwing sphinx for a loop. I'll try to do a local test setup tomorrow.

@akanstantsinau
Copy link
Contributor Author

@dopry I believe sphinx issue is something not related with changes in this PR:

  • According to build log , dependency sphinx-rtd-theme is not installed
  • sphinx_rtd_theme extension is required in docs/conf.py
  • That causes build failure with No module named 'sphinx_rtd_theme' error

The fix might be adding sphinx-rtd-theme dependency to docs/requirements.txt
New versions of sphinx and readthedocs-sphinx-ext were released in September 2023, so it may make things changed.

I'd prefer not to fix it in this PR, because its a different issue. Also my findings can be not correct.

Working on coverage issue.

@rcoup
Copy link

rcoup commented Oct 4, 2023

ah, don't suppose django-oauth-toolkit is hit by the recent RTD changes? https://blog.readthedocs.com/defaulting-latest-build-tools/ or https://blog.readthedocs.com/newsletter-september-2023/

@dopry
Copy link
Contributor

dopry commented Oct 5, 2023

@rcoup looks like you're right. I'll try to get a PR up to fix that today so we can get CI working again.

@dopry
Copy link
Contributor

dopry commented Oct 5, 2023

@akanstantsinau I updated the branch with a rebase in the Github UI to see if the RTD fixes are good. Feel free to force push your local over it.

@dopry
Copy link
Contributor

dopry commented Oct 5, 2023

@akanstantsinau it looks like everything is passing. I'll do some manual testing later today and hopefully we'll get this in.

@akanstantsinau
Copy link
Contributor Author

@dopry that are awesome news! Thank you!

oauth2_provider/models.py Outdated Show resolved Hide resolved
Copy link
Contributor

@dopry dopry left a comment

Choose a reason for hiding this comment

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

I wasn't able to do my local testing due to validation issues.

oauth2_provider/models.py Outdated Show resolved Hide resolved
@akanstantsinau
Copy link
Contributor Author

@dopry Added oauth_settings.ALLOWED_SCHEMES with default set to ["https"] and updated validation for allowed_origins

@akanstantsinau akanstantsinau requested a review from dopry October 19, 2023 14:23
akanstantsinau and others added 4 commits October 19, 2023 14:40
It is possible to control CORS by overriding is_origin_allowed method
of RequestValidator class.
OAuthLib allows origin if:
- is_origin_allowed returns True for particular request
- Request connection is secure
- Request has 'Origin' header
@dopry dopry merged commit 2c83e6c into jazzband:master Oct 19, 2023
26 checks passed
@dopry dopry mentioned this pull request Oct 20, 2023
3 tasks
@dopry dopry mentioned this pull request Nov 10, 2023
5 tasks
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.

5 participants