Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions tests/test_sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,40 @@ def test_get_profile_and_token_returns_expected_workosprofile_object(
assert profile_and_token.access_token == "01DY34ACQTM3B1CSX1YSZ8Z00D"
assert profile_and_token.profile.to_dict() == mock_profile

def test_get_profile_and_token_from_id_token_returns_expected_workosprofile_object(
self, setup_with_client_id, mock_profile, mock_request_method
):
response_dict = {
"profile": {
"object": "profile",
"id": mock_profile["id"],
"email": mock_profile["email"],
"first_name": mock_profile["first_name"],
"groups": mock_profile["groups"],
"organization_id": mock_profile["organization_id"],
"connection_id": mock_profile["connection_id"],
"connection_type": mock_profile["connection_type"],
"last_name": mock_profile["last_name"],
"idp_id": mock_profile["idp_id"],
"raw_attributes": {
"email": mock_profile["raw_attributes"]["email"],
"first_name": mock_profile["raw_attributes"]["first_name"],
"last_name": mock_profile["raw_attributes"]["last_name"],
"groups": mock_profile["raw_attributes"]["groups"],
},
},
"access_token": "01DY34ACQTM3B1CSX1YSZ8Z00D",
}

mock_request_method("post", response_dict, 200)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Token-exchange payload remains untested

This mock verifies only response deserialization and never inspects the outgoing request, so incorrect grant_type, subject_token, subject_token_type, or organization_id values can pass the test and fail only against the live API. Assert the endpoint, method, and exact JSON body for this new flow.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/test_sso.py
Line: 452

Comment:
**Token-exchange payload remains untested**

This mock verifies only response deserialization and never inspects the outgoing request, so incorrect `grant_type`, `subject_token`, `subject_token_type`, or `organization_id` values can pass the test and fail only against the live API. Assert the endpoint, method, and exact JSON body for this new flow.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.


profile_and_token = self.sso.get_profile_and_token_from_id_token(
"eyJhbGciOiJSUzI1NiJ9.id.token", "org_01EHQMYV6MBK39QC5PZXHY59C3"
)

assert profile_and_token.access_token == "01DY34ACQTM3B1CSX1YSZ8Z00D"
assert profile_and_token.profile.to_dict() == mock_profile

def test_get_profile_and_token_without_first_name_or_last_name_returns_expected_workosprofile_object(
self, setup_with_client_id, mock_magic_link_profile, mock_request_method
):
Expand Down
2 changes: 1 addition & 1 deletion workos/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

__package_url__ = "https://github.com/workos-inc/workos-python"

__version__ = "4.15.0"
__version__ = "4.16.0"

__author__ = "WorkOS"

Expand Down
35 changes: 35 additions & 0 deletions workos/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
PROFILE_PATH = "sso/profile"

OAUTH_GRANT_TYPE = "authorization_code"
TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
ID_TOKEN_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token"

RESPONSE_LIMIT = 10

Expand Down Expand Up @@ -162,6 +164,39 @@ def get_profile_and_token(self, code):

return WorkOSProfileAndToken.construct_from_response(response)

def get_profile_and_token_from_id_token(self, id_token, organization_id):
"""Exchange an externally issued OIDC ID token for a Profile and Token

For flows where the user authenticates natively with the identity
provider (for example a mobile app using MSAL against Microsoft Entra)
and no browser redirect occurs. The ID token is exchanged for the same
WorkOS profile the authorization code flow would return. The connection
must have an ID token trust configured for the token's issuer and
audience.

Args:
id_token (str): The OIDC ID token issued to the client.
organization_id (str): The organization whose connection the ID
token should be validated against.

Returns:
WorkOSProfileAndToken: WorkOSProfileAndToken object representing the User
"""
params = {
"client_id": workos.client_id,
"client_secret": workos.api_key,
"grant_type": TOKEN_EXCHANGE_GRANT_TYPE,
"subject_token": id_token,
"subject_token_type": ID_TOKEN_SUBJECT_TOKEN_TYPE,
"organization_id": organization_id,
}

response = self.request_helper.request(
TOKEN_PATH, method=REQUEST_METHOD_POST, params=params
)

return WorkOSProfileAndToken.construct_from_response(response)

def get_connection(self, connection):
"""Gets details for a single Connection

Expand Down
Loading