Skip to content

fix(selfhost): fetch SSO UserInfo for thin ID tokens - #2041

Open
askalf wants to merge 8 commits into
UsefulSoftwareCo:mainfrom
askalf:fix/sso-userinfo-email-verified-fallback
Open

askalf wants to merge 8 commits into
UsefulSoftwareCo:mainfrom
askalf:fix/sso-userinfo-email-verified-fallback

Conversation

@askalf

@askalf askalf commented Sep 18, 2026

Copy link
Copy Markdown

Summary

A thin OIDC ID token can omit email_verified, which the self-hosted SSO callback needs. ssoProviderConfig now supplies getUserInfo, so the claim is read from the provider's UserInfo endpoint when the token lacks it.

The resolver treats the IdP as an external boundary throughout:

  • UserInfo claims are used only when their sub matches the ID token's sub; a profile for another subject returns null.
  • A supplied ID token that does not decode, or carries no sub, is declined. Only an absent ID token is resolved through UserInfo alone.
  • sub and email count only as non-empty strings, on both paths.
  • emailVerified is always a boolean: true only for a literal true claim. An explicit false or null in the ID token is honoured without a UserInfo lookup, as an unverified email.
  • A rejected discovery or UserInfo request, or rejected JSON parsing, returns null the same way a non-OK response already did, instead of rejecting the callback.

Linked issue

Fixes #1972

Verification

apps/host-selfhost/src/auth/sso-userinfo.test.ts: 22 cases, run with bun run test in apps/host-selfhost (22 passed). They cover the thin-token UserInfo path, the subject mismatch, declined malformed and subject-less tokens, wrongly typed claims, empty subject / email / access token / userinfo_endpoint, the four rejection points (each with a fresh response and an asserted request count), and admission at the gate. oxlint --deny-warnings and oxfmt --check are clean on the touched files.

askalf and others added 8 commits September 15, 2026 22:23
A UserInfo profile is used only when its sub matches the ID token's; a
supplied ID token that does not decode or carries no sub is declined
instead of being treated as absent; sub and email count only as
non-empty strings; and emailVerified is a boolean on both paths, true
only for a literal true claim.

The rejection test builds a fresh discovery response per case and
creates its rejections only when fetch is called, and asserts the
number of requests each case makes.
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] SSO fails with any IdP that returns email_verified only from /userinfo

1 participant