Skip to content

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

Closed
askalf wants to merge 10 commits into
UsefulSoftwareCo:mainfrom
sprayberry-code:fix/sso-userinfo-email-verified-fallback
Closed

askalf wants to merge 10 commits into
UsefulSoftwareCo:mainfrom
sprayberry-code: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 to admit a user. ssoProviderConfig now supplies getUserInfo, so the claim is read from the provider's UserInfo endpoint when the ID token lacks it.

  • UserInfo claims are used only when their sub matches the ID token's sub. A supplied ID token that does not decode, or has no sub, is declined.
  • emailVerified is true only for a literal true claim. An explicit false in the ID token is honoured without a UserInfo lookup.
  • A failed or rejected discovery or UserInfo request returns null instead of rejecting the callback.

Linked issue

Fixes #1972

Verification

apps/host-selfhost/src/auth/sso-userinfo.test.ts has one case: a thin ID token with email but no email_verified is resolved through UserInfo and admitted at the gate. vitest run src/auth/sso-userinfo.test.ts in apps/host-selfhost fails on main with TypeError: ssoProviderConfig(...).getUserInfo is not a function and passes with the fix. The Okta emulator always signs email_verified into its ID tokens, so the test stubs the two IdP responses. oxfmt --check and oxlint --deny-warnings are clean on the touched files.

Changeset: .changeset/selfhost-sso-userinfo.md (patch for @executor-js/host-selfhost and executor).

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.
@Silentphantom62

Copy link
Copy Markdown

Thanks @askalf! @RhysSullivan can we can include this in the next patch release.

@askalf

askalf commented Oct 8, 2026

Copy link
Copy Markdown
Author

I cut the tests down to make this easier to review. sso.ts is unchanged since 10a34f1; the diff is now +129/-0.

sso-userinfo.test.ts went from 22 cases to 1 (536 lines to 39): a thin ID token with email but no email_verified is resolved through UserInfo and admitted at the gate. It fails on main with getUserInfo is not a function and passes with the fix. The Okta emulator always signs email_verified into its ID tokens, so the test stubs the two IdP responses.

I also added the changeset the PR template asks for (.changeset/selfhost-sso-userinfo.md, patch).

Happy to reshape it if you'd like it another way.

@RhysSullivan

Copy link
Copy Markdown
Collaborator

We're clearing the backlog ahead of the v2 launch, so we're closing this. If it still applies to v2, please open a new issue or PR against v2.

Sent from my Claude

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

3 participants