Skip to content

Commit 7aae761

Browse files
authored
Add PKCE parameters to UM auth flow (#275)
* Add PKCE parameters to UM auth flow * Format with black * Refactor params to automatically set the code challenge method
1 parent 2171558 commit 7aae761

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

tests/test_user_management.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,28 @@ def test_authorization_url_has_expected_query_params_with_state(self):
621621
"response_type": RESPONSE_TYPE_CODE,
622622
}
623623

624+
def test_authorization_url_has_expected_query_params_with_code_challenge(self):
625+
connection_id = "connection_123"
626+
redirect_uri = "https://localhost/auth/callback"
627+
code_challenge = json.dumps({"code_challenge": "code_challenge_for_pkce"})
628+
629+
authorization_url = self.user_management.get_authorization_url(
630+
connection_id=connection_id,
631+
code_challenge=code_challenge,
632+
redirect_uri=redirect_uri,
633+
)
634+
635+
parsed_url = urlparse(authorization_url)
636+
637+
assert dict(parse_qsl(parsed_url.query)) == {
638+
"code_challenge": code_challenge,
639+
"code_challenge_method": "S256",
640+
"client_id": workos.client_id,
641+
"redirect_uri": redirect_uri,
642+
"connection_id": connection_id,
643+
"response_type": RESPONSE_TYPE_CODE,
644+
}
645+
624646
def test_authenticate_with_password(
625647
self, capture_and_mock_request, mock_auth_response
626648
):
@@ -653,13 +675,15 @@ def test_authenticate_with_password(
653675

654676
def test_authenticate_with_code(self, capture_and_mock_request, mock_auth_response):
655677
code = "test_code"
678+
code_verifier = "test_code_verifier"
656679
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
657680
ip_address = "192.0.0.1"
658681

659682
url, request = capture_and_mock_request("post", mock_auth_response, 200)
660683

661684
response = self.user_management.authenticate_with_code(
662685
code=code,
686+
code_verifier=code_verifier,
663687
user_agent=user_agent,
664688
ip_address=ip_address,
665689
)
@@ -670,6 +694,7 @@ def test_authenticate_with_code(self, capture_and_mock_request, mock_auth_respon
670694
assert response["access_token"] == "access_token_12345"
671695
assert response["refresh_token"] == "refresh_token_12345"
672696
assert request["json"]["code"] == code
697+
assert request["json"]["code_verifier"] == code_verifier
673698
assert request["json"]["user_agent"] == user_agent
674699
assert request["json"]["ip_address"] == ip_address
675700
assert request["json"]["client_id"] == "client_b27needthisforssotemxo"

workos/user_management.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ def get_authorization_url(
423423
domain_hint=None,
424424
login_hint=None,
425425
state=None,
426+
code_challenge=None,
426427
):
427428
"""Generate an OAuth 2.0 authorization URL.
428429
@@ -444,6 +445,7 @@ def get_authorization_url(
444445
OktaSAML, and AzureSAML connection types. (Optional)
445446
state (str) - An encoded string passed to WorkOS that'd be preserved through the authentication workflow, passed
446447
back as a query parameter. (Optional)
448+
code_challenge (str) - Code challenge is derived from the code verifier used for the PKCE flow. (Optional)
447449
448450
Returns:
449451
str: URL to redirect a User to to begin the OAuth workflow with WorkOS
@@ -476,6 +478,9 @@ def get_authorization_url(
476478
params["login_hint"] = login_hint
477479
if state is not None:
478480
params["state"] = state
481+
if code_challenge:
482+
params["code_challenge"] = code_challenge
483+
params["code_challenge_method"] = "S256"
479484

480485
prepared_request = Request(
481486
"GET",
@@ -534,13 +539,16 @@ def authenticate_with_password(
534539
def authenticate_with_code(
535540
self,
536541
code,
542+
code_verifier=None,
537543
ip_address=None,
538544
user_agent=None,
539545
):
540546
"""Authenticates an OAuth user or a user that is logging in through SSO.
541547
542548
Kwargs:
543549
code (str): The authorization value which was passed back as a query parameter in the callback to the Redirect URI.
550+
code_verifier (str): The randomly generated string used to derive the code challenge that was passed to the authorization
551+
url as part of the PKCE flow. This parameter is required when the client secret is not present. (Optional)
544552
ip_address (str): The IP address of the request from the user who is attempting to authenticate. (Optional)
545553
user_agent (str): The user agent of the request from the user who is attempting to authenticate. (Optional)
546554
@@ -565,6 +573,9 @@ def authenticate_with_code(
565573
if user_agent:
566574
payload["user_agent"] = user_agent
567575

576+
if code_verifier:
577+
payload["code_verifier"] = code_verifier
578+
568579
response = self.request_helper.request(
569580
USER_AUTHENTICATE_PATH,
570581
method=REQUEST_METHOD_POST,

0 commit comments

Comments
 (0)