This repository was archived by the owner on Apr 26, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Support PKCE for OAuth 2.0 #14750
Merged
Merged
Support PKCE for OAuth 2.0 #14750
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
32475f0
Support OIDC code challenges.
clokep 4aad25e
Newsfragment
clokep 5e910b8
Changes from review.
clokep 1e82afd
Fix outdated comment.
clokep 45d3bba
Clarify comments.
clokep 1ebd224
Clarify comment.
clokep 8682a71
Merge branch 'develop' into clokep/oidc-code-challenges
clokep d504e47
Support disabling PKCE.
clokep 08df5d4
Merge branch 'develop' into clokep/oidc-code-challenges
clokep File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Support [RFC7636](https://datatracker.ietf.org/doc/html/rfc7636) Proof Key for Code Exchange for OAuth single sign-on. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,7 @@ | |
| from authlib.jose.errors import InvalidClaimError, JoseError, MissingClaimError | ||
| from authlib.oauth2.auth import ClientAuth | ||
| from authlib.oauth2.rfc6749.parameters import prepare_grant_uri | ||
| from authlib.oauth2.rfc7636.challenge import create_s256_code_challenge | ||
| from authlib.oidc.core import CodeIDToken, UserInfo | ||
| from authlib.oidc.discovery import OpenIDProviderMetadata, get_well_known_url | ||
| from jinja2 import Environment, Template | ||
|
|
@@ -403,6 +404,7 @@ def __init__( | |
| provider.client_auth_method, | ||
| ) | ||
| self._client_auth_method = provider.client_auth_method | ||
| self._code_challenge_method = provider.code_challenge_method | ||
|
|
||
| # cache of metadata for the identity provider (endpoint uris, mostly). This is | ||
| # loaded on-demand from the discovery endpoint (if discovery is enabled), with | ||
|
|
@@ -475,6 +477,20 @@ def _validate_metadata(self, m: OpenIDProviderMetadata) -> None: | |
| ) | ||
| ) | ||
|
|
||
| # Validate code_challenge_methods_supported only if it is enabled. | ||
| if ( | ||
| m.get("code_challenge_methods_supported") is not None | ||
| and self._code_challenge_method is not None | ||
| ): | ||
| m.validate_code_challenge_methods_supported() | ||
| if self._code_challenge_method not in m["code_challenge_methods_supported"]: | ||
| raise ValueError( | ||
| '"{code_challenge_method}" not in "code_challenge_methods_supported" ({supported!r})'.format( | ||
| code_challenge_method=self._code_challenge_method, | ||
| supported=m["code_challenge_methods_supported"], | ||
| ) | ||
| ) | ||
|
|
||
| if m.get("response_types_supported") is not None: | ||
| m.validate_response_types_supported() | ||
|
|
||
|
|
@@ -653,7 +669,7 @@ async def _load_jwks(self) -> JWKS: | |
|
|
||
| return jwk_set | ||
|
|
||
| async def _exchange_code(self, code: str) -> Token: | ||
| async def _exchange_code(self, code: str, code_verifier: str) -> Token: | ||
| """Exchange an authorization code for a token. | ||
|
|
||
| This calls the ``token_endpoint`` with the authorization code we | ||
|
|
@@ -666,6 +682,7 @@ async def _exchange_code(self, code: str) -> Token: | |
|
|
||
| Args: | ||
| code: The authorization code we got from the callback. | ||
| code_verifier: The code verifier to send, blank if unused. | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Returns: | ||
| A dict containing various tokens. | ||
|
|
@@ -696,6 +713,8 @@ async def _exchange_code(self, code: str) -> Token: | |
| "code": code, | ||
| "redirect_uri": self._callback_url, | ||
| } | ||
| if code_verifier: | ||
| args["code_verifier"] = code_verifier | ||
| body = urlencode(args, True) | ||
|
|
||
| # Fill the body/headers with credentials | ||
|
|
@@ -935,17 +954,38 @@ async def handle_redirect_request( | |
|
|
||
clokep marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| state = generate_token() | ||
| nonce = generate_token() | ||
| code_verifier = "" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wondered why you weren't making that optional, but then I remembered we rely on macaroons for those... :'(
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeahhhh, I figured this was the cleanest way to do it. |
||
|
|
||
| if not client_redirect_url: | ||
| client_redirect_url = b"" | ||
|
|
||
| # If code challenge is supported and configured, use it. | ||
| extra_grant_values = {} | ||
| if self._code_challenge_method is not None: | ||
| code_verifier = generate_token(48) | ||
|
|
||
| # Default to the values for plain. | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| extra_grant_values = {"code_challenge_method": self._code_challenge_method} | ||
|
|
||
| if self._code_challenge_method == "S256": | ||
| extra_grant_values["code_challenge"] = create_s256_code_challenge( | ||
| code_verifier | ||
| ) | ||
| elif self._code_challenge_method == "plain": | ||
| extra_grant_values["code_challenge"] = code_verifier | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| else: | ||
| raise RuntimeError( | ||
| f"Unexpeced code challenege method: {self._code_challenge_method!r}" | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) | ||
|
|
||
| cookie = self._macaroon_generaton.generate_oidc_session_token( | ||
| state=state, | ||
| session_data=OidcSessionData( | ||
| idp_id=self.idp_id, | ||
| nonce=nonce, | ||
| client_redirect_url=client_redirect_url.decode(), | ||
| ui_auth_session_id=ui_auth_session_id or "", | ||
| code_verifier=code_verifier, | ||
| ), | ||
| ) | ||
|
|
||
|
|
@@ -976,6 +1016,7 @@ async def handle_redirect_request( | |
| scope=self._scopes, | ||
| state=state, | ||
| nonce=nonce, | ||
| **extra_grant_values, | ||
| ) | ||
|
|
||
| async def handle_oidc_callback( | ||
|
|
@@ -1003,7 +1044,9 @@ async def handle_oidc_callback( | |
| # Exchange the code with the provider | ||
| try: | ||
| logger.debug("Exchanging OAuth2 code for a token") | ||
| token = await self._exchange_code(code) | ||
| token = await self._exchange_code( | ||
| code, code_verifier=session_data.code_verifier | ||
| ) | ||
| except OidcError as e: | ||
| logger.warning("Could not exchange OAuth2 code: %s", e) | ||
| self._sso_handler.render_error(request, e.error, e.error_description) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.