Skip to content

Query OIDC's UserInfo endpoint for authorotative claims - #2266

Open
tecosaur wants to merge 2 commits into
drakkan:mainfrom
tecosaur:oidc-userinfo-claims
Open

Query OIDC's UserInfo endpoint for authorotative claims#2266
tecosaur wants to merge 2 commits into
drakkan:mainfrom
tecosaur:oidc-userinfo-claims

Conversation

@tecosaur

Copy link
Copy Markdown

This is something that's niggled me ever since I set up SFTPGo and discovered it was one of the handful of OIDC clients that hasn't quite faithfully implemented the OIDC spec and needs a workaround.

As per OIDC Core §5.3, the authoritative way to receive OIDC claims is through the userinfo endpoint. Currently, SFTPGo doesn't query this, and relies on all claims being shoved into the token.

So, I've gone ahead and added userinfo reading to the OIDC client implementation. Specifically, unless id_token_claims_only is set (only needed in case of broken OIDC providers), we try to access the userinfo endpoint, and upon a response:

  • check that the response is valid, otherwise abort the login (something is wrong)
  • merge the userinfo claims with the token's

It goes without saying that auth needs to be handled with care; this isn't the first time I've contributed OIDC improvements. I've upstreamed OIDC implementations/improvements to a handful of projects at this point, and consulted the Authelia maintainer to make sure I had the right understanding of how token/userinfo merging should be handled and what's a decent approach to non-conformant OIDC providers.

I got an LLM to both help with reviewing this work, and also write tests for it.

Closes #2265

Checklist for Pull Requests

SFTPGo reads all OIDC claims from the ID token doesn't query the
UserInfo endpoint, forcing every provider to be configured to place
profile claims (preferred_username, email, role, ...) into the ID
token (e.g. an Authelia claims_policy). This is not a faithful
implementation of OIDC: the UserInfo endpoint is the authoritative
source for profile claims, so a stock install could not authenticate
against a compliant provider.

After verifying the ID token, SFTPGo now queries the UserInfo endpoint
and overlays its claims over the ID token claims (UserInfo wins on
conflict; except for the Back-Channel Logout "sid"). An unreachable
endpoint is an availability failure and falls back to the ID token
claims; a UserInfo response that is reached but cannot be trusted (its
subject differs from the ID token subject, or its body cannot be parsed)
rejects the login. The new id_token_claims_only option skips the request
for providers that fail to support UserInfo.
Query the UserInfo endpoint only when the provider's discovery document
advertises one, using the userinfo_endpoint exposed by go-oidc's
Provider.UserInfoEndpoint(). An unadvertised endpoint is expected
(RECOMMENDED, not REQUIRED per OIDC Discovery): and now only uses the id
token claims rather than attempting a request that fails.

Once the endpoint is advertised the provider is asserting UserInfo as
part of the identity contract, so a failure to query and trust it now
rejects the login instead of falling back: a fetch error, a missing
access token, a subject mismatch, or an unparseable body are all
treated as integrity failures. The id_token_claims_only option, which
returns before any request, is the escape hatch for a provider whose
advertised endpoint is broken or undesirable.
@drakkan

drakkan commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Thanks for the update, the overall structure looks good. I just did a quick review, so I may be wrong on some points, I'm also open to discuss the proposed changes, not strong opinions.

  • Please rename the setting to query_userinfo. I would also prefer false as the default: existing working setups keep working unchanged after upgrading.
  • Add env var support for the new setting in internal/config/config.go, see for example SFTPGO_HTTPD__BINDINGS__%v__OIDC__DEBUG.
  • Merge precedence: if the default ends up being true, the ID token should win on conflicts. With UserInfo taking precedence and enabled by default, an upgrade may silently change the resolved username_field/role_field values for existing setups: for example a Keycloak deployment with a custom mapper overriding preferred_username with only "Add to ID token" enabled, a possible configuration given that SFTPGo has always read claims from the ID token.
  • Also assuming true as the default: with custom scopes, the access token may not be valid at the UserInfo endpoint (e.g. Entra ID with scopes for a custom API, rare but possible). Those deployments will get login failures after upgrading until they set query_userinfo to false. The endpoint gating doesn't help here, since the endpoint is advertised but rejects the token. At a minimum this needs to be clearly documented in the release notes (If I recall about this 😄)

@tecosaur

Copy link
Copy Markdown
Author

Thanks for giving this a look at your thoughts. I'll be able to act on your feedback in a couple of days.

Please rename the setting to query_userinfo

👍

Add env var support for the new setting in internal/config/config.go, see for example SFTPGO_HTTPD__BINDINGS__%v__OIDC__DEBUG.

👍 thanks for the pointer

I would also prefer false as the default

From a "default to unchanged" behaviour here, I absolutely see why this is what comes to mind as the most conservative approach. It just does very much seem to me like the wrong default, from my understanding of the flow OIDC Core seems to encourage (which may well be incorrect/incomplete).

Considering the providers that I know of/have taken a peek at (as mentioned in the issue)/seen docs on, it looks like reading userinfo by default when advertised:

  • Causes more correct behaviour when the provider has meaningful extra claims in the userinfo
  • Has no impact when the userinfo simply provides the bare minimum (as Microsoft Entra ID seems to)

I think the only possible scenarios where reading userinfo could cause an issue is when:

  • Userinfo is advertised but returns something invalid, or
  • Userinfo values conflict with token values

These sound like instances of a provider that would fail OpenID certification and/or are misconfigured, i.e. instances when it's probably actually good to fail.

@james-d-elliott it would be useful to hear if this sounds like the right interpretation/assesment of the situation to you?

@drakkan

drakkan commented Jul 21, 2026

Copy link
Copy Markdown
Owner

@tecosaur For SFTPGo Enterprise, I'll be conservative because I don't want to break existing deployments, and the codebase is already different anyway.

For the community edition here, I'm fine with enabling the userinfo endpoint by default. These changes will be included in the next major release, so a potential breaking change is acceptable. I just need to remember to mention it in the release notes.

The overall implementation looks fine but I need more time to properly review. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Non-compliant OIDC claim reading

2 participants