-
Notifications
You must be signed in to change notification settings - Fork 582
feat: add PKCE for Oauth2 OIDC #9545
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
base: main
Are you sure you want to change the base?
Conversation
Thanks @anderska for the contribution. The team is heads down on feature work and I'm not deeply familiar with PKSE, so it might take a minute, but we'll take a look. |
- USE_PKCE boolean with S256 hardcoded (replaces CODE_CHALLENGE_METHOD) - Make CLIENT_SECRET optional for public PKCE clients - GROUPS_ATTRIBUTE_PATH + ALLOWED_GROUPS with JMESPath for flexible claim extraction - ID token -> userinfo fallback for missing claims
Thanks for your PKCE implementation! I've made several design changes that I wanted to explain: 1.
|
# and the code_verifier cookie exists | ||
if oauth2_client.use_pkce and code_verifier: | ||
fetch_kwargs["code_verifier"] = code_verifier | ||
token_data = await oauth2_client.fetch_access_token(**fetch_kwargs) |
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.
Bug: PKCE Code Verifier Missing Causes Token Rejection
When PKCE is enabled for an OAuth2 client, the code_verifier
is only included in the token exchange if its cookie is present. If the cookie is missing, the request proceeds without the required verifier, causing the OIDC server to reject the token. This results in a generic 'oauth_error' for the user, obscuring the specific failure.
"Group-based access control requires both parameters to be set." | ||
) | ||
|
||
self._compiled_groups_path = self._compile_jmespath_expression(self._groups_attribute_path) |
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.
Bug: Initialization Error and Group Name Mismatch
The OAuth2Client
initialization fails with an AttributeError
when group-based access control is configured, as _groups_attribute_path
is accessed before it's assigned. Additionally, group names in allowed_groups
are not stripped of whitespace, which can lead to authorization mismatches with claims from the identity provider.
"error_description": "code_verifier not allowed when PKCE is not enabled", | ||
}, | ||
status_code=400, | ||
) |
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.
Bug: OIDC Server PKCE Validation Flaws
The mock OIDC server's /token
endpoint has inconsistent code_verifier
validation. In non-PKCE flows, code_verifier
rejection happens after client authentication, potentially returning invalid_client
instead of invalid_request
and leaking information. The check also fails to reject empty code_verifier
strings. When PKCE is enabled, invalid client credentials result in a generic invalid_client
error.
client_secret = _get_optional("CLIENT_SECRET") | ||
else: | ||
# Traditional flow: client_secret is required | ||
client_secret = _get_required("CLIENT_SECRET", "Client secret") |
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.
Bug: PKCE Misconfiguration Causes Security and Runtime Issues
Enabling use_pkce
makes client_secret
optional, which can lead to two issues. Confidential clients might unintentionally omit their secret, weakening their security posture. Additionally, if the token_endpoint_auth_method
expects a secret (e.g., client_secret_basic
), passing a None
client_secret
can cause runtime errors during token exchange.
if g and g not in seen: | ||
allowed_groups.append(g) | ||
seen.add(g) | ||
|
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.
Bug: Group Parsing Fails with Spaces
The ALLOWED_GROUPS
environment variable is parsed in from_env
using re.split(r"[,\s]+", raw_groups)
. This regex incorrectly splits group names containing spaces (e.g., 'admin users' becomes 'admin' and 'users'), which can lead to authorization bypasses. It also treats multiple consecutive delimiters as a single separator, potentially leading to unexpected parsing.
OAuth2: configurable token auth, PKCE support, required group claims, and safer claim parsing
Summary
This PR enhances the OAuth2/OIDC integration with:
Changes compared to main
OAuth2 configuration (src/phoenix/config.py)
openid email profile
.PHOENIX_OAUTH2_{IDP}_REQUIRED_GROUPS
groups roles custom_claim
).required_groups
for server-side enforcement.PHOENIX_OAUTH2_{IDP}_TOKEN_ENDPOINT_AUTH_METHOD
client_secret_basic
(default),client_secret_post
,none
.PHOENIX_OAUTH2_{IDP}_CODE_CHALLENGE_METHOD
S256
(recommended) orplain
.OAuth2 client (src/phoenix/server/oauth2.py)
scope
from config,required_groups
(for downstream logic),token_endpoint_auth_method
when set,code_challenge_method
when set.AsyncHttpxOAuth2Client
with the above kwargs.Router and auth flow (src/phoenix/server/api/routers/oauth2.py)
code_verifier
in a cookie and sends it on token exchange.parse_user_info
: buildsclaims
containing only non-empty values (filters outNone
, empty strings/containers).required_groups
) exist and are non-empty; otherwise raisesSignInNotAllowed
.New/updated environment variables
PHOENIX_OAUTH2_{IDP}_REQUIRED_GROUPS
groups roles team
).PHOENIX_OAUTH2_{IDP}_TOKEN_ENDPOINT_AUTH_METHOD
client_secret_basic
(default),client_secret_post
,none
.PHOENIX_OAUTH2_{IDP}_CODE_CHALLENGE_METHOD
S256
(recommended) orplain
.Examples
Note
Adds PKCE support and group-based access control to OAuth2/OIDC with new env-driven configuration, safer claim parsing, and comprehensive tests.
code_verifier
cookie; sendscode_verifier
on token exchange; cleans up cookies on success/error._parse_user_info
with validation, normalized claims, and fallback to userinfo when needed.GROUPS_ATTRIBUTE_PATH
) andALLOWED_GROUPS
; validates presence and membership.TOKEN_ENDPOINT_AUTH_METHOD
and PKCE (S256
); extracts/normalizes groups; precompiles JMESPath; addshas_sufficient_claims
andvalidate_access
.src/phoenix/config.py
):OAuth2ClientConfig
: optionalclient_secret
(PKCE),use_pkce
,token_endpoint_auth_method
,scopes
,groups_attribute_path
,allowed_groups
.src/phoenix/auth.py
):phoenix-oauth2-code-verifier
cookie; cleanup on redirects.jmespath
andtypes-jmespath
.Written by Cursor Bugbot for commit 070e167. This will update automatically on new commits. Configure here.