fix(auth): keep userGuid on session after better-auth 1.6 upgrade + broken-session recovery#20
Merged
Merged
Conversation
….6 regression) better-auth 1.6 changed how additional user fields are parsed from the OAuth provider profile: a user additionalField declared with input:false no longer flows through when a value is supplied (in 1.6.11 the parser throws "userGuid is not allowed to be set"). Our userGuid field (MP User_GUID / OAuth sub) is populated server-side via mapProfileToUser, so input:false silently broke it: session.user.userGuid became undefined, breaking every MP profile lookup - blank avatar, dead user menu. - Flip userGuid to input:true; extract to exported userAdditionalFields const. - Add a regression guard test running the real better-auth parse function (parseAdditionalUserInput from better-auth/db) against the real field config. - Correct auth reference docs (user-identity.md, sessions.md), add a Better Auth upgrade checklist and elevate the in-memory DB-adapter warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g the user A session without a userGuid rendered a dead header - no avatar/menu, no sign-out control, user stuck. Add a defensive guard so an unusable session always has an exit, independent of root cause. - AuthWrapper: session with no userGuid -> redirect to /session-error. - New /session-error page: recovery screen with a handleSignOut form button, outside the (web) route group so it can't redirect-loop. - auth-wrapper.test.tsx covers all guard branches (no session -> /signin, no userGuid -> /session-error, null userGuid -> /session-error, healthy). - Document the recovery flow in route-protection.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
better-auth 1.6 changed how additional user fields are parsed from the OAuth
provider profile. A user
additionalFielddeclared withinput: falsenolonger flows through when a value is supplied — in 1.6.11 the parser
(
parseAdditionalUserInput, formerlyparseAdditionalUserInputFromProviderProfile)throws
"userGuid is not allowed to be set".Our
userGuidfield (MPUser_GUID/ OAuthsub) is populated server-sidevia
mapProfileToUser, soinput: falsebroke it:session.user.userGuidbecame
undefined, which breaks every MP profile lookup — blank header avatar,dead (non-interactive) user menu, and no way to sign out.
This shipped via the routine bump of better-auth from 1.4.x → 1.6.x. CI (build +
lint + unit tests) does not exercise a real OAuth login, so it passed silently.
Changes
Fix (
fix(auth)commit)userGuidtoinput: true; extract to an exporteduserAdditionalFieldsconst so a test can assert against the real config. Safe here: the app uses
genericOAuthonly — no email/password signup or update-user endpoint, so thefield is only ever set server-side from the verified OAuth profile.
src/auth.test.tsthat runs the real better-authparser (
parseAdditionalUserInputfrombetter-auth/db) against the realfield config — fails if someone flips it back OR a future better-auth version
changes provider-profile parsing again.
user-identity.md,sessions.md), add a BetterAuth upgrade checklist, and elevate the in-memory DB-adapter warning.
Recovery (
feat(auth)commit)AuthWrapper: a session with nouserGuidnow redirects to/session-errorinstead of rendering a dead app with no sign-out.
/session-errorpage: recovery screen with ahandleSignOutform button,placed outside the
(web)route group so it can't redirect-loop.auth-wrapper.test.tsxcovers all branches (no session →/signin,no/
nulluserGuid→/session-error, healthy → children).Verification
npm run lint— cleannpm run test:run— 670/670 pass, incl. the new real-library guardnpm run build— succeeds; emits the/session-errorroutenot
userGuidactually reaches the session. After merge: sign out, log infresh, open
/api/auth/get-session, confirmuser.userGuidis present and theavatar/menu render with a working sign-out.
🤖 Generated with Claude Code