Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions changelog.d/18232.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for the `login_hint` parameter in OIDC authentication flow to pre-fill user identification.
7 changes: 6 additions & 1 deletion synapse/handlers/oidc.py
Copy link
Member

Choose a reason for hiding this comment

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

This would be an extension of the spec, which should go through the spec change process. Given that it's unlikely that such a spec change would land, I think it would make sense to instead have a generic option to 'passthrough' specific query parameters.

Something like

oidc_providers:
  - idp_id: 
    passthrough_authorization_parameters:
      - login_hint

And then passthrough any query parameter passed to /_matrix/client/v3/login/sso/redirect to the OIDC authorization request

Copy link
Contributor Author

@odelcroi odelcroi Apr 2, 2025

Choose a reason for hiding this comment

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

thanks for your inputs, makes sense, I've made the corresponding changes

Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ async def handle_redirect_request(
- ``state``: a random string
- ``nonce``: a random string
- ``code_challenge``: a RFC7636 code challenge (if PKCE is supported)
- ``login_hint``: provide login to avoid re-entering it in the upstream idp

In addition to generating a redirect URL, we are setting a cookie with
a signed macaroon token containing the state, the nonce, the
Expand All @@ -1005,7 +1006,6 @@ async def handle_redirect_request(
when everything is done (or None for UI Auth)
ui_auth_session_id: The session ID of the ongoing UI Auth (or
None if this is a login).

Returns:
The redirect URL to the authorization endpoint.

Expand Down Expand Up @@ -1077,6 +1077,11 @@ async def handle_redirect_request(
options,
)
)
# gather login_hint from query string
login_hint = parse_string(request, "login_hint")

if login_hint:
additional_authorization_parameters.update({"login_hint": login_hint})

authorization_endpoint = metadata.get("authorization_endpoint")
return prepare_grant_uri(
Expand Down
19 changes: 14 additions & 5 deletions tests/handlers/test_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
CALLBACK_URL = BASE_URL + "_synapse/client/oidc/callback"
TEST_REDIRECT_URI = "https://test/oidc/callback"
SCOPES = ["openid"]
LOGIN_HINT = "[email protected]"

# config for common cases
DEFAULT_CONFIG = {
Expand Down Expand Up @@ -439,8 +440,10 @@ def test_skip_verification(self) -> None:
@override_config({"oidc_config": DEFAULT_CONFIG})
def test_redirect_request(self) -> None:
"""The redirect request has the right arguments & generates a valid session cookie."""
req = Mock(spec=["cookies"])
req = Mock(spec=["cookies", "args"])
req.cookies = []
req.args = {}
req.args[b"login_hint"] = [LOGIN_HINT.encode("utf-8")]

url = urlparse(
self.get_success(
Expand All @@ -461,6 +464,8 @@ def test_redirect_request(self) -> None:
self.assertEqual(len(params["state"]), 1)
self.assertEqual(len(params["nonce"]), 1)
self.assertNotIn("code_challenge", params)
# Verify that login_hint is properly set
self.assertEqual(params["login_hint"], [LOGIN_HINT])

# Check what is in the cookies
self.assertEqual(len(req.cookies), 2) # two cookies
Expand All @@ -487,8 +492,9 @@ def test_redirect_request(self) -> None:
@override_config({"oidc_config": DEFAULT_CONFIG})
def test_redirect_request_with_code_challenge(self) -> None:
"""The redirect request has the right arguments & generates a valid session cookie."""
req = Mock(spec=["cookies"])
req = Mock(spec=["cookies", "args"])
req.cookies = []
req.args = {}

with self.metadata_edit({"code_challenge_methods_supported": ["S256"]}):
url = urlparse(
Expand Down Expand Up @@ -522,8 +528,9 @@ def test_redirect_request_with_code_challenge(self) -> None:
@override_config({"oidc_config": {**DEFAULT_CONFIG, "pkce_method": "always"}})
def test_redirect_request_with_forced_code_challenge(self) -> None:
"""The redirect request has the right arguments & generates a valid session cookie."""
req = Mock(spec=["cookies"])
req = Mock(spec=["cookies", "args"])
req.cookies = []
req.args = {}

url = urlparse(
self.get_success(
Expand Down Expand Up @@ -554,8 +561,9 @@ def test_redirect_request_with_forced_code_challenge(self) -> None:
@override_config({"oidc_config": {**DEFAULT_CONFIG, "pkce_method": "never"}})
def test_redirect_request_with_disabled_code_challenge(self) -> None:
"""The redirect request has the right arguments & generates a valid session cookie."""
req = Mock(spec=["cookies"])
req = Mock(spec=["cookies", "args"])
req.cookies = []
req.args = {}

# The metadata should state that PKCE is enabled.
with self.metadata_edit({"code_challenge_methods_supported": ["S256"]}):
Expand Down Expand Up @@ -592,8 +600,9 @@ def test_redirect_request_with_disabled_code_challenge(self) -> None:
)
def test_redirect_request_with_overridden_redirect_uri(self) -> None:
"""The authorization endpoint redirect has the overridden `redirect_uri` value."""
req = Mock(spec=["cookies"])
req = Mock(spec=["cookies", "args"])
req.cookies = []
req.args = {}

url = urlparse(
self.get_success(
Expand Down
Loading