Skip to content

fix: always emit email and name claims in the planner id_token - #83

Open
mroderick wants to merge 3 commits into
mainfrom
fix/id-token-email-claim
Open

mroderick wants to merge 3 commits into
mainfrom
fix/id-token-email-claim

Conversation

@mroderick

@mroderick mroderick commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Users signing in through the auth app could be dropped into the planner's new-member flow, losing their subscriptions and roles, because the planner received an id_token without an email claim and fell back to sub (the better-auth user id). This fix makes every id_token carry the user's email and name, and bumps better-auth to 1.7.7.

  • customIdTokenClaims now always emits email and name from the user record, for every session type (previously it only returned github_id)
  • better-auth and @better-auth/oauth-provider bumped 1.7.5 → 1.7.7
  • Test suite aligned with production config and now covers the regression
Background and mechanism

The planner's codebar OmniAuth strategy resolves the member by the id_token's email claim, falling back to sub when the claim is absent:

email = payload['email'] || payload['sub']

better-auth 1.7 stopped including user-record claims (email, name) in the id_token for sessions not created through a social provider. A user who signed in by magic link therefore got a planner member whose email and name were the better-auth user id — an empty account with no subscriptions or roles. Two such duplicate members were created in production on 2026-10-02; the incident was mitigated by rolling back the auth deploy.

The missing user-record claims affect any session without provider data; the explicit claims in customIdTokenClaims are spread after the library's default claims, so they are always present.

A separate integration test reproduces the scenario: magic-link session → authorize → token exchange → id_token claims. Under 1.7.5/1.7.7 without this change, the email and name claims are absent; with it, they match the user record.

Review notes

Focus first on the claims semantics: every id_token now carries the better-auth user-record email. For users whose GitHub primary email differs from their stored planner email, the planner now matches (or keys a new member) on the user-record email, with the github_id claim still available to resolve returning members. Check that interplay for accounts where the two emails diverge.

Also worth a look: the lockfile diff is now minimal. Only the better-auth family moves 1.7.5 to 1.7.7 (plus one @types/node patch float from peer materialisation), and better-auth is pinned exactly to 1.7.7 to match the @better-auth/oauth-provider pin. Review fixes landed on top: the claims builder is shared between src/auth.js and the test helper, the linked-GitHub test asserts email and name, and the oauth-flow test registers teardown.

Deliberately not done here: hardening the planner's || payload['sub'] fallback (separate planner change), and setting the client's scopes column in the seed (authorize falls back to the plugin's allowed scopes).

@mroderick
mroderick force-pushed the fix/id-token-email-claim branch 2 times, most recently from 7e06a8b to e0d0228 Compare October 3, 2026 07:59
@mroderick
mroderick changed the base branch from main to chore/fallow-3-31-dead-code October 3, 2026 07:59
@mroderick
mroderick added this pull request to stack #85 October 3, 2026 08:05
@mroderick
mroderick marked this pull request as ready for review October 3, 2026 08:05
@mroderick
mroderick marked this pull request as draft October 3, 2026 08:06
@mroderick
mroderick force-pushed the fix/id-token-email-claim branch from e0d0228 to ce12016 Compare October 3, 2026 08:08
Base automatically changed from chore/fallow-3-31-dead-code to main October 3, 2026 10:25
@mroderick
mroderick force-pushed the fix/id-token-email-claim branch 3 times, most recently from d4036bd to c7f6afb Compare October 3, 2026 13:40
@mroderick

Copy link
Copy Markdown
Collaborator Author

Tracked in #87.

@mroderick
mroderick marked this pull request as ready for review October 3, 2026 14:02
@mroderick
mroderick marked this pull request as draft October 3, 2026 14:33
@mroderick
mroderick force-pushed the fix/id-token-email-claim branch from c7f6afb to 232dcd1 Compare October 3, 2026 14:48
Both packages are pinned exactly so patch-level claim changes cannot float in
noticed. The lockfile diff is limited to the better-auth family (1.7.5 -> 1.7.7)
plus one `@types/node` patch float from peer materialisation; no unrelated
runtime deps move.
Users signing in through the auth app could be dropped into the planner's new-member flow, losing 
their subscriptions and roles: the planner resolves members by the id_token `email` claim and falls 
back to `sub` (the better-auth user id) when the claim is absent, and better-auth 1.7 stopped 
including user-record claims for sessions without provider data. Two duplicate members were created 
in production on 2026-10-02.

`customIdTokenClaims` now always emits `email` and `name` from the user record, keeping `github_id` 
for returning-member resolution. The claims builder lives in `src/auth/id-token-claims.js` and is 
shared with the test helper, so the regression tests exercise the code production runs; the test 
instance is aligned with the production scopes list.

Tests cover the magic-link path (email/name asserted against the user record) and the linked-GitHub 
path (all three claims in one payload), and the oauth-flow test now registers tap teardown so it 
stops leaking a schema per run.
The marker is a working note, not documentation. Keep the explanation of why `skipStateCookieCheck` 
is set.

@till till left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be fixed if the planner requested email explicitly?

@mroderick

mroderick commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator Author

Would this be fixed if the planner requested email explicitly?

No. Me and my synthetic research assistant checked the 1.7.7 source to be sure

This branch has not been deployed

No deployments
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.

2 participants