feat(auth): OIDC userinfo fallback for email / email_verified (#105) #106
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!106
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "105-oidc-userinfo-fallback"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Authentik (and a few other IdPs) gate
email/email_verifiedbehind a per-provider "Include claims in id_token" toggle. When off, those claims live only at the userinfo endpoint and the id_token carries just standard claims. Carol previously rejected such users with?error=oidc_claim— even for users the IdP WAS willing to attest as verified, just not on the id_token.Carol now widens where it looks for the claim. The verified-email boundary doesn't move:
email_verified === trueis still required.Flow
emailoremail_verified, call the userinfo endpoint with theaccess_tokenfrom the same token-exchange.subto equal the id_tokensub(OIDC §5.3.2 — token-substitution defence).email_verified === truecheck.access_token, nouserinfo_endpointconfigured) and the id_token lacks the claim, still?error=oidc_claim.Trust chain
access_token— IdP authenticates Carol because the IdP just issued the access_token to Carol's client_id.submatch binds the userinfo response to the same subject the id_token signed off on — a stolen access_token for a different user can't be substituted.email_verified === truestill load-bearing (ADR-0015 §3 inherited).Full rationale in ADR-0017 §4.5.
Test plan
npm run typecheck— clean.npm run lint— clean.npm test— 225 passed / 38 skipped (was 215 / 38 — 10 new tests).verifyIdTokennow returns{providerUserId, email?, emailVerified?}; 3 new tests cover claim-present-but-explicit-false, claim-missing-entirely, both-missing.fetchUserinfounit: returns email + verified on sub match; rejects on sub mismatch (token substitution); rejects on non-200; returns undefined on missing claims.tests/api/oauth.test.ts(+5): id_token lacksemail_verified+ userinfo provides it → session set; id_token lacks both + userinfo provides both → session set; userinfo sub mismatch →oidc_claim; userinfo also lacks claim →oidc_claim; noaccess_tokento call userinfo →oidc_claim.carol.int.wynning.techafter release — sign-in clears without changing Authentik's claims toggle.Files
lib/auth/oidc-verify.ts—verifyIdTokenshape change + newfetchUserinfohelper.lib/auth/oidc-providers.ts— orchestratedextractProfile.docs/adr/0017-oidc-generic-provider.md— new §4.5; §3 threat #13 updated.docs/oidc-self-hoster-guide.md— Troubleshooting entry.tests/auth/oidc-verify.test.ts— updated +fetchUserinfotests.tests/api/oauth.test.ts— end-to-end fallback scenarios.Closes #105.
🤖 Generated with Claude Code