collective / collective/pas.plugins.oidc
Allow merging selected `id_token` claims into `userinfo` response
- Dominant language
- Python
- Stars
- 6
- Forks
- 16
- PR merge metrics
- No merged PRs in 30d
Description
In some OpenID Connect providers (e.g. Microsoft Entra ID / Azure AD), the `userinfo_endpoint` returns a limited subset of the claims that are already present in the `id_token`.
Currently, `pas.plugins.oidc` retrieves the `id_token` claims from the token response, but if a `userinfo_endpoint` is configured, the call to the endpoint replaces the previously extracted claims.
This means that attributes available in the `id_token` may be lost if they are not also returned by the userinfo endpoint.
I am not sure if this is an issue with the configuration of my provider or if I did something wrong, but I overcome this issue by patching [the `get_user_info` function](https://github.com/collective/pas.plugins.oidc/blob/7c3fe5fed7ae4f961a35f0af4d636838023a707a/src/pas/plugins/oidc/utils.py#L177-L207):
```python
def get_user_info(client, state, args, method="POST") -> message.OpenIDSchema | dict:
resp = client.do_access_token_request(
state=state,
request_args=args,
authn_method="client_secret_basic",
)
user_info = {}
if isinstance(resp, message.AccessTokenResponse):
# If it's an AccessTokenResponse the information in the response will be stored
# in the client instance with state as the key for future use.
id_token = user_info = resp.to_dict().get("id_token", {})
if client.userinfo_endpoint:
# https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
try:
user_info = client.do_user_info_request(method=method, state=state)
except RequestError as exc:
logger.error(
"Authentication failed, probably missing openid scope",
exc_info=exc,
)
user_info = {}
#
_name_mapping = {
"foo": "bar"
}
_extended_user_info = {
_name_mapping.get(key, key): id_token.get(key)
for key in (
"foo",
"preferred_username",
)
if key in id_token and key not in user_info
}
user_info.update(_extended_user_info)
#
# userinfo in an instance of OpenIDSchema or ErrorResponse
# It could also be dict, if there is no userinfo_endpoint
if not (user_info and isinstance(user_info, message.OpenIDSchema | dict)):
logger.error(
"Authentication failed, invalid response %s %s", resp, user_info
)
user_info = {}
elif isinstance(resp, message.TokenErrorResponse):
logger.error("Token error response: %s", resp.to_json())
else:
logger.error("Authentication failed %s", resp)
return user_info
```
It would be nice to have that mapping configurable in the plugin properties.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.