Skip to content

Commit 93c4fa4

Browse files
gjtorikianclaude
andauthored
fix(user_management): honor client_id in PKCE authorization URL (#701)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent d94f9f8 commit 93c4fa4

3 files changed

Lines changed: 38 additions & 4 deletions

File tree

src/workos/sso/_resource.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ def get_authorization_url(
145145
*,
146146
provider_scopes: Optional[List[str]] = None,
147147
provider_query_params: Optional[Dict[str, str]] = None,
148+
client_id: Optional[str] = None,
148149
domain: Optional[str] = None,
149150
provider: Optional[Union[SSOProvider, str]] = None,
150151
redirect_uri: str,
@@ -164,6 +165,7 @@ def get_authorization_url(
164165
Args:
165166
provider_scopes: Additional scopes to request from the identity provider. Applicable when using OAuth or OpenID Connect connections.
166167
provider_query_params: Key/value pairs of query parameters to pass to the OAuth provider. Only applicable when using OAuth connections.
168+
client_id: The unique identifier of the WorkOS environment client. Defaults to the client's configured client_id.
167169
domain: (deprecated) Deprecated. Use `connection` or `organization` instead. Used to initiate SSO for a connection by domain. The domain must be associated with a connection in your WorkOS environment.
168170
provider: Used to initiate OAuth authentication with various providers.
169171
redirect_uri: Where to redirect the user after they complete the authentication process. You must use one of the redirect URIs configured via the [Redirects](https://dashboard.workos.com/redirects) page on the dashboard.
@@ -193,6 +195,7 @@ def get_authorization_url(
193195
if provider_scopes is not None
194196
else None,
195197
"provider_query_params": provider_query_params,
198+
"client_id": client_id,
196199
"domain": domain,
197200
"provider": enum_value(provider) if provider is not None else None,
198201
"redirect_uri": redirect_uri,
@@ -207,7 +210,7 @@ def get_authorization_url(
207210
if v is not None
208211
}
209212
params["response_type"] = "code"
210-
if self._client.client_id is not None:
213+
if "client_id" not in params and self._client.client_id is not None:
211214
params["client_id"] = self._client.client_id
212215
return self._client.build_url(("sso", "authorize"), params)
213216

@@ -565,6 +568,7 @@ def get_authorization_url(
565568
*,
566569
provider_scopes: Optional[List[str]] = None,
567570
provider_query_params: Optional[Dict[str, str]] = None,
571+
client_id: Optional[str] = None,
568572
domain: Optional[str] = None,
569573
provider: Optional[Union[SSOProvider, str]] = None,
570574
redirect_uri: str,
@@ -584,6 +588,7 @@ def get_authorization_url(
584588
Args:
585589
provider_scopes: Additional scopes to request from the identity provider. Applicable when using OAuth or OpenID Connect connections.
586590
provider_query_params: Key/value pairs of query parameters to pass to the OAuth provider. Only applicable when using OAuth connections.
591+
client_id: The unique identifier of the WorkOS environment client. Defaults to the client's configured client_id.
587592
domain: (deprecated) Deprecated. Use `connection` or `organization` instead. Used to initiate SSO for a connection by domain. The domain must be associated with a connection in your WorkOS environment.
588593
provider: Used to initiate OAuth authentication with various providers.
589594
redirect_uri: Where to redirect the user after they complete the authentication process. You must use one of the redirect URIs configured via the [Redirects](https://dashboard.workos.com/redirects) page on the dashboard.
@@ -613,6 +618,7 @@ def get_authorization_url(
613618
if provider_scopes is not None
614619
else None,
615620
"provider_query_params": provider_query_params,
621+
"client_id": client_id,
616622
"domain": domain,
617623
"provider": enum_value(provider) if provider is not None else None,
618624
"redirect_uri": redirect_uri,
@@ -627,7 +633,7 @@ def get_authorization_url(
627633
if v is not None
628634
}
629635
params["response_type"] = "code"
630-
if self._client.client_id is not None:
636+
if "client_id" not in params and self._client.client_id is not None:
631637
params["client_id"] = self._client.client_id
632638
return self._client.build_url(("sso", "authorize"), params)
633639

src/workos/user_management/_resource.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ def get_authorization_url(
565565
state: Optional[str] = None,
566566
organization_id: Optional[str] = None,
567567
redirect_uri: str,
568+
client_id: Optional[str] = None,
568569
request_options: Optional[RequestOptions] = None,
569570
) -> str:
570571
"""Get an authorization URL
@@ -587,6 +588,7 @@ def get_authorization_url(
587588
state: An opaque value used to maintain state between the request and the callback.
588589
organization_id: The ID of the organization to authenticate the user against.
589590
redirect_uri: The callback URI where the authorization code will be sent after authentication.
591+
client_id: The unique identifier of the WorkOS environment client. Defaults to the client's configured client_id.
590592
request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override.
591593
592594
Returns:
@@ -619,11 +621,12 @@ def get_authorization_url(
619621
"state": state,
620622
"organization_id": organization_id,
621623
"redirect_uri": redirect_uri,
624+
"client_id": client_id,
622625
}.items()
623626
if v is not None
624627
}
625628
params["response_type"] = "code"
626-
if self._client.client_id is not None:
629+
if "client_id" not in params and self._client.client_id is not None:
627630
params["client_id"] = self._client.client_id
628631
return self._client.build_url(("user_management", "authorize"), params)
629632

@@ -2373,6 +2376,7 @@ def get_authorization_url_with_pkce(
23732376

23742377
url = self.get_authorization_url(
23752378
redirect_uri=redirect_uri,
2379+
client_id=client_id,
23762380
code_challenge=pair.code_challenge,
23772381
code_challenge_method="S256",
23782382
state=state,
@@ -2924,6 +2928,7 @@ def get_authorization_url(
29242928
state: Optional[str] = None,
29252929
organization_id: Optional[str] = None,
29262930
redirect_uri: str,
2931+
client_id: Optional[str] = None,
29272932
request_options: Optional[RequestOptions] = None,
29282933
) -> str:
29292934
"""Get an authorization URL
@@ -2946,6 +2951,7 @@ def get_authorization_url(
29462951
state: An opaque value used to maintain state between the request and the callback.
29472952
organization_id: The ID of the organization to authenticate the user against.
29482953
redirect_uri: The callback URI where the authorization code will be sent after authentication.
2954+
client_id: The unique identifier of the WorkOS environment client. Defaults to the client's configured client_id.
29492955
request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override.
29502956
29512957
Returns:
@@ -2978,11 +2984,12 @@ def get_authorization_url(
29782984
"state": state,
29792985
"organization_id": organization_id,
29802986
"redirect_uri": redirect_uri,
2987+
"client_id": client_id,
29812988
}.items()
29822989
if v is not None
29832990
}
29842991
params["response_type"] = "code"
2985-
if self._client.client_id is not None:
2992+
if "client_id" not in params and self._client.client_id is not None:
29862993
params["client_id"] = self._client.client_id
29872994
return self._client.build_url(("user_management", "authorize"), params)
29882995

@@ -4732,6 +4739,7 @@ async def get_authorization_url_with_pkce(
47324739

47334740
url = self.get_authorization_url(
47344741
redirect_uri=redirect_uri,
4742+
client_id=client_id,
47354743
code_challenge=pair.code_challenge,
47364744
code_challenge_method="S256",
47374745
state=state,

tests/test_inline_helpers.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,19 @@ def test_with_organization_id(self, workos):
9494
)
9595
assert "organization_id=org_01" in result["url"]
9696

97+
def test_uses_configured_client_id(self, workos):
98+
result = workos.user_management.get_authorization_url_with_pkce(
99+
redirect_uri="https://example.com/callback"
100+
)
101+
assert "client_id=client_test" in result["url"]
102+
103+
def test_explicit_client_id_overrides_configured(self, workos):
104+
result = workos.user_management.get_authorization_url_with_pkce(
105+
redirect_uri="https://example.com/callback", client_id="client_override"
106+
)
107+
assert "client_id=client_override" in result["url"]
108+
assert "client_id=client_test" not in result["url"]
109+
97110

98111
@pytest.mark.asyncio
99112
class TestAsyncAuthKitPKCEAuthorizationUrl:
@@ -112,6 +125,13 @@ async def test_url_contains_pkce_params(self, async_workos):
112125
assert "code_challenge=" in result["url"]
113126
assert "code_challenge_method=S256" in result["url"]
114127

128+
async def test_explicit_client_id_overrides_configured(self, async_workos):
129+
result = await async_workos.user_management.get_authorization_url_with_pkce(
130+
redirect_uri="https://example.com/callback", client_id="client_override"
131+
)
132+
assert "client_id=client_override" in result["url"]
133+
assert "client_id=client_test" not in result["url"]
134+
115135

116136
class TestAuthKitPKCECodeExchange:
117137
def test_sends_code_verifier(self, workos, httpx_mock):

0 commit comments

Comments
 (0)