Skip to content

Commit 32aea77

Browse files
workos-sdk-automation[bot]gjtorikianclaude
authored
fix: forward Radar context from authenticate_with_code_pkce (#620)
Co-authored-by: Garen J. Torikian <gjtorikian@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2f85a70 commit 32aea77

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

src/workos/user_management/_resource.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,6 +2197,9 @@ def authenticate_with_code_pkce(
21972197
code: str,
21982198
code_verifier: str,
21992199
client_id: Optional[str] = None,
2200+
ip_address: Optional[str] = None,
2201+
device_id: Optional[str] = None,
2202+
user_agent: Optional[str] = None,
22002203
request_options: Optional[RequestOptions] = None,
22012204
) -> AuthenticateResponse:
22022205
"""Exchange an authorization code using a PKCE code_verifier.
@@ -2205,6 +2208,9 @@ def authenticate_with_code_pkce(
22052208
code: The authorization code received from the redirect.
22062209
code_verifier: The PKCE code verifier generated alongside the code challenge.
22072210
client_id: The WorkOS client ID. Defaults to the client's configured ID.
2211+
ip_address: The IP address of the user's request.
2212+
device_id: A unique identifier for the device.
2213+
user_agent: The user agent string from the user's browser.
22082214
request_options: Per-request options for headers, timeout, retries, or base URL.
22092215
22102216
Returns:
@@ -2219,6 +2225,12 @@ def authenticate_with_code_pkce(
22192225
}
22202226
if self._client._api_key:
22212227
body["client_secret"] = self._client._api_key
2228+
if ip_address is not None:
2229+
body["ip_address"] = ip_address
2230+
if device_id is not None:
2231+
body["device_id"] = device_id
2232+
if user_agent is not None:
2233+
body["user_agent"] = user_agent
22222234

22232235
return cast(
22242236
AuthenticateResponse,
@@ -4361,6 +4373,9 @@ async def authenticate_with_code_pkce(
43614373
code: str,
43624374
code_verifier: str,
43634375
client_id: Optional[str] = None,
4376+
ip_address: Optional[str] = None,
4377+
device_id: Optional[str] = None,
4378+
user_agent: Optional[str] = None,
43644379
request_options: Optional[RequestOptions] = None,
43654380
) -> AuthenticateResponse:
43664381
"""Exchange an authorization code using a PKCE code_verifier.
@@ -4369,6 +4384,9 @@ async def authenticate_with_code_pkce(
43694384
code: The authorization code received from the redirect.
43704385
code_verifier: The PKCE code verifier generated alongside the code challenge.
43714386
client_id: The WorkOS client ID. Defaults to the client's configured ID.
4387+
ip_address: The IP address of the user's request.
4388+
device_id: A unique identifier for the device.
4389+
user_agent: The user agent string from the user's browser.
43724390
request_options: Per-request options for headers, timeout, retries, or base URL.
43734391
43744392
Returns:
@@ -4383,6 +4401,12 @@ async def authenticate_with_code_pkce(
43834401
}
43844402
if self._client._api_key:
43854403
body["client_secret"] = self._client._api_key
4404+
if ip_address is not None:
4405+
body["ip_address"] = ip_address
4406+
if device_id is not None:
4407+
body["device_id"] = device_id
4408+
if user_agent is not None:
4409+
body["user_agent"] = user_agent
43864410

43874411
return cast(
43884412
AuthenticateResponse,

tests/test_inline_helpers.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,32 @@ def test_includes_client_secret_when_api_key_present(self, workos, httpx_mock):
135135
body = json.loads(request.content)
136136
assert "client_secret" in body
137137

138+
def test_forwards_radar_context(self, workos, httpx_mock):
139+
httpx_mock.add_response(json=load_fixture("authenticate_response.json"))
140+
workos.user_management.authenticate_with_code_pkce(
141+
code="auth_code_123",
142+
code_verifier="test_verifier_abc",
143+
ip_address="203.0.113.42",
144+
device_id="device_01HXYZ",
145+
user_agent="Mozilla/5.0",
146+
)
147+
request = httpx_mock.get_request()
148+
body = json.loads(request.content)
149+
assert body["ip_address"] == "203.0.113.42"
150+
assert body["device_id"] == "device_01HXYZ"
151+
assert body["user_agent"] == "Mozilla/5.0"
152+
153+
def test_omits_radar_context_when_not_provided(self, workos, httpx_mock):
154+
httpx_mock.add_response(json=load_fixture("authenticate_response.json"))
155+
workos.user_management.authenticate_with_code_pkce(
156+
code="auth_code_123", code_verifier="test_verifier_abc"
157+
)
158+
request = httpx_mock.get_request()
159+
body = json.loads(request.content)
160+
assert "ip_address" not in body
161+
assert "device_id" not in body
162+
assert "user_agent" not in body
163+
138164

139165
@pytest.mark.asyncio
140166
class TestAsyncAuthKitPKCECodeExchange:
@@ -148,6 +174,34 @@ async def test_sends_code_verifier(self, async_workos, httpx_mock):
148174
body = json.loads(request.content)
149175
assert body["code_verifier"] == "test_verifier_abc"
150176

177+
async def test_forwards_radar_context(self, async_workos, httpx_mock):
178+
httpx_mock.add_response(json=load_fixture("authenticate_response.json"))
179+
await async_workos.user_management.authenticate_with_code_pkce(
180+
code="auth_code_123",
181+
code_verifier="test_verifier_abc",
182+
ip_address="203.0.113.42",
183+
device_id="device_01HXYZ",
184+
user_agent="Mozilla/5.0",
185+
)
186+
request = httpx_mock.get_request()
187+
body = json.loads(request.content)
188+
assert body["ip_address"] == "203.0.113.42"
189+
assert body["device_id"] == "device_01HXYZ"
190+
assert body["user_agent"] == "Mozilla/5.0"
191+
192+
async def test_omits_radar_context_when_not_provided(
193+
self, async_workos, httpx_mock
194+
):
195+
httpx_mock.add_response(json=load_fixture("authenticate_response.json"))
196+
await async_workos.user_management.authenticate_with_code_pkce(
197+
code="auth_code_123", code_verifier="test_verifier_abc"
198+
)
199+
request = httpx_mock.get_request()
200+
body = json.loads(request.content)
201+
assert "ip_address" not in body
202+
assert "device_id" not in body
203+
assert "user_agent" not in body
204+
151205

152206
class TestSSOPKCEAuthorizationUrl:
153207
def test_returns_required_keys(self, workos):

0 commit comments

Comments
 (0)