BuiltSafely

Home/Fixes

Trends

better-auth OAuth sign-in advisories: check your version and upgrade to 1.7.7

Four better-auth advisories cover OAuth sign-in with Magic Link, the OAuth Proxy plugin, device login and PostgreSQL rate limits. Check the version your lockfile installs, upgrade to 1.7.7 or later, redeploy every server together, then revoke sessions you don't trust.

  • Docs-only
  • Reviewed Oct 2026
  • 16 official sources
Sources Last checked October 27, 2026 · Badge: docs-only (better-auth's GitHub security advisories, v1.7.7 release notes and better-auth docs; we did not reproduce the vulnerabilities). The version check, upgrade, config search and session revocation steps are verified on throwaway local projects · Sources: linked below · Educational, not an audit

Short version: if better-auth is below 1.7.7, upgrade to 1.7.7 or any later 1.7.x release, then rebuild and redeploy every server that shares your auth database or proxy setup at the same time. The most serious issue (critical, CVE-2026-114607) lets someone who knows a user's email address sign in as that user when Magic Link and OAuth sign-in are both turned on (GHSA-965c-763c-88jm).

The fix

Upgrade, redeploy, then clean up sessions. The v1.7.7 release notes list a "critical Magic Link account-takeover vulnerability" fix and an OAuth Proxy fix, and say no database migration is needed (better-auth v1.7.7 release notes).

npm install better-auth@latest     # or: pnpm add / bun add / yarn add better-auth@latest
npm ls better-auth                 # want 1.7.7 or newer

When we checked, the latest release on npm was 1.7.14. Any 1.7.x release from 1.7.7 up includes the fixes. Then:

  1. Rebuild and redeploy every server that shares the same auth database or secondary storage in one cutover (Docs-only).
  2. If you use the OAuth Proxy plugin, upgrade every production, preview and development deployment that takes part in the proxy flow together (Docs-only).
  3. Revoke sessions for any account you don't trust (Verified on a throwaway local project, see step 5 below).

The four advisories

All four are on better-auth's GitHub repository. Each one only applies when specific features are turned on, listed in the last column. Version ranges and severities are copied from the advisories.

IssueSeverityAdvisoryAffected → fixedYou're only exposed if
OAuth sign-in state can be used as a Magic LinkCritical (9.1)GHSA-965c-763c-88jm, CVE-2026-114607better-auth 1.4.0-beta.18 up to 1.7.6 → 1.7.7All three: the Magic Link plugin stores tokens as "plain" (the default), or as "hashed" with the global verification.storeIdentifier also "hashed"; a social provider (socialProviders) or Generic OAuth provider is on; and account.storeStateStrategy is "database" (the default when you have a database or secondary storage).
OAuth Proxy lets an attacker sign in as another userHigh (7.4)GHSA-r4xp-prcw-77qf, no CVE listedbetter-auth 1.5.0-beta.12 up to 1.7.6 → 1.7.7All three: the oAuthProxy plugin has no dedicated secret (or uses the same value as your main secret); account.storeStateStrategy is "cookie"; and implicit account linking is on (the default).
Device login approval page doesn't show which app is askingHigh (8.1)GHSA-q84f-53jg-9ppm, no CVE listedbetter-auth 1.3.8-beta.3 up to before 1.7.0-rc.3 → 1.7.0You use the deviceAuthorization plugin. An attacker still needs a signed-in user to approve their code, so this is phishing, not a silent bypass.
Rate limits can be exceeded by concurrent requestsLow (3.7)GHSA-44jh-23m7-hpcf, no CVE listed@better-auth/drizzle-adapter and better-auth 1.6.17 up to 1.7.6 → 1.7.7; @better-auth/kysely-adapter 1.6.17 → 1.7.0Rate limiting is on and stored in the database (rateLimit.storage: "database") on PostgreSQL. The default in-memory store and secondary storage are not affected.

Two of these are not 1.7.7 fixes. The device login fix shipped in 1.7.0, and the advisory says "No 1.6 release contains the fix." The Kysely adapter part of the rate-limit issue was also fixed in 1.7.0. Either way, 1.7.7 or later covers all four.

If two advisory pages disagree: when we checked, the copies of GHSA-965c-763c-88jm, GHSA-r4xp-prcw-77qf and GHSA-44jh-23m7-hpcf in GitHub's global advisory database still described 1.7.7 as a planned release that wasn't available. The copies on better-auth's own repository say to upgrade to 1.7.7. The v1.7.7 release is published and installable from npm. Follow the repository copies.

Step by step

1. Find the version you actually run (Verified on throwaway npm, pnpm, Bun and Yarn projects). Run the line for your package manager in the project folder:

npm ls better-auth @better-auth/drizzle-adapter @better-auth/kysely-adapter
pnpm list better-auth
pnpm why @better-auth/drizzle-adapter
bun pm ls
yarn list --pattern better-auth      # Yarn 1

The lockfile is what your build installs, so check it too. In our tests the installed version showed up like this:

LockfileSearch forWhat 1.7.6 looked like
package-lock.json"node_modules/better-auth"the next line reads "version": "1.7.6"
pnpm-lock.yamlbetter-auth@better-auth@1.7.6:
yarn.lockbetter-auth@an entry followed by version "1.7.6"
bun.lock"better-auth""better-auth": ["better-auth@1.7.6", …]
grep -n -A1 '"node_modules/better-auth"' package-lock.json
grep -n "better-auth@" pnpm-lock.yaml yarn.lock bun.lock 2>/dev/null

npm audit also flags these. On a 1.7.6 project it reported the Magic Link, OAuth Proxy and rate-limit advisories (one critical, one low). On a 1.6.33 project it reported all four. After upgrading to 1.7.7 it reported 0 vulnerabilities (Verified).

2. Upgrade to 1.7.7 or later (Verified for npm, pnpm, Bun and Yarn going from 1.7.6 to 1.7.7):

npm install better-auth@latest
pnpm add better-auth@latest
bun add better-auth@latest
yarn add better-auth@latest

In our npm test, upgrading better-auth also moved the Drizzle and Kysely adapters it pulls in to 1.7.7. If you list any @better-auth/* package yourself in package.json, upgrade it in the same command. The advisories say to keep adapters and plugins released alongside better-auth "on the same release version" (Docs-only: we didn't test a project with separately listed adapters). In our test, Yarn 1 refused to install on Node 20 because a dependency (kysely) requires Node 22 or newer. Check which Node version your project and host use before you upgrade.

On 1.6.x or 1.4.x? The advisories list 1.7.x as the only fixed line, and npm audit suggested a 1.7 release for our 1.6.33 project. Moving from 1.6 to 1.7 can need database preparation, for example the Device Authorization plugin needs duplicate codes cleaned up before its unique indexes are added. Read the better-auth 1.7 upgrade guide first. It also documents an npx auth upgrade command, which needs Node.js 22.12 or newer (Docs-only: we didn't run it).

3. Rebuild and redeploy everything at once (Docs-only). Commit the updated lockfile, then deploy. From the advisories and release notes:

  • Upgrade every server that shares the same verification storage (your auth database or secondary storage) in one cutover.
  • OAuth Proxy: upgrade every production, preview and development deployment that takes part in the proxy flow together. Mixed versions can't exchange sign-in state (better-auth: OAuth Proxy, upgrading existing deployments).
  • Magic Links sent before the upgrade stop working. Users request a new one (better-auth: Magic Link). Anyone partway through an OAuth or SAML sign-in starts again.
  • If you set verification.storeIdentifier.overrides, update them for the magic-link: and auth-state: prefixes, as the GHSA-965c-763c-88jm patch notes describe.

4. Check which of the affected features you use (Verified: this search found each setting in a sample auth config and skipped node_modules). Run it in your project root:

grep -rnE "socialProviders|genericOAuth\(|magicLink\(|oAuthProxy\(|deviceAuthorization\(|storeStateStrategy|storeToken|storeIdentifier|rateLimit" \
  --include='*.ts' --include='*.tsx' --include='*.js' --include='*.mjs' --include='*.cjs' \
  --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist .

How to read the results (option names from the better-auth options reference):

  • socialProviders or genericOAuth(: you have OAuth sign-in (Generic OAuth plugin).
  • magicLink( with no storeToken, or storeToken: "plain": Magic Link is on with the default token storage. Combined with OAuth sign-in and no storeStateStrategy: "cookie", this matches GHSA-965c-763c-88jm.
  • oAuthProxy( with no secret: the plugin falls back to your main secret (OAuth Proxy plugin). Combined with storeStateStrategy: "cookie", this matches GHSA-r4xp-prcw-77qf, unless you set account.accountLinking.disableImplicitLinking: true.
  • deviceAuthorization(: you use device login (Device Authorization plugin). See step 6.
  • rateLimit with storage: "database" on PostgreSQL: matches GHSA-44jh-23m7-hpcf (better-auth: rate limit storage).

No matches for a feature means you don't use it in that code. Config can also live in shared packages or be built from environment variables, so check those too. Upgrade either way: the version is the fix.

5. Revoke sessions you don't trust (Verified: on a throwaway local project with better-auth 1.7.14, each call below ended the sessions it should and left the others signed in). The advisories don't tell you to revoke sessions. But if your setup matched one of them, someone could have gotten a normal signed-in session. A session lasts 7 days by default and is extended when used, until it expires or is revoked (better-auth: session management).

// A signed-in user, from your client code
await authClient.revokeOtherSessions()   // sign out everywhere except this device
await authClient.revokeSessions()        // sign out everywhere

// An admin, with the admin plugin, for one user
await authClient.admin.revokeUserSessions({ userId: "user-id" })

The admin call comes from the better-auth admin plugin, which also has listUserSessions to see a user's sessions first. In our test a non-admin calling it got a "FORBIDDEN" error. If you turned on session.cookieCache, the docs warn that revoked sessions "may remain active" on other devices until the cache maxAge runs out. Shorten maxAge or turn the cache off while you clean up (Docs-only).

Secrets: the OAuth Proxy advisory says the upgrade "does not require rotating that secret." If you ran the OAuth Proxy plugin with no dedicated secret, give it one now: the same OAUTH_PROXY_SECRET on every environment in the proxy flow, different from BETTER_AUTH_SECRET. If you decide to rotate your main secret anyway, use the secrets option so existing data can still be read during the switch (better-auth: secret rotation). Docs-only.

The Magic Link advisory also warns that, with sign-up on, an attacker could create an account marked as email-verified for someone else's address. If your app gives extra access based on a verified email or email domain, review those accounts.

6. Device login: show who is asking (Docs-only). From 1.7.0, the device code lookup (GET /device) returns the client_id and requested scope. If you built your own approval page, show both before the user can approve, and keep the approve button off until they've loaded. The plugin docs list this under production security requirements (better-auth: Device Authorization). You can also limit which clients may start device login with validateClient.

Can't upgrade yet?

Each advisory lists workarounds (Docs-only). They're stopgaps, and two of them pull in opposite directions:

  • Magic Link (GHSA-965c-763c-88jm): set account.storeStateStrategy: "cookie", or set Magic Link storeToken: "hashed" with the global storeIdentifier unset or "plain", or turn off Magic Link or OAuth sign-in. Magic Link disableSignUp alone is not enough.
  • OAuth Proxy (GHSA-r4xp-prcw-77qf): give the plugin a dedicated secret, or keep sign-in state in the database, or remove the plugin. disableImplicitLinking alone is not enough.
  • Device login (GHSA-q84f-53jg-9ppm): turn off the plugin, or limit clients with validateClient and build an approval page that shows the client and scopes.
  • Rate limits (GHSA-44jh-23m7-hpcf): move rate-limit storage to secondary storage, use a custom store whose consume is atomic, or add a limit in front of your app.

Switching to cookie state closes the Magic Link issue but is one of the conditions for the OAuth Proxy issue. If you use both plugins, set a dedicated proxy secret before you switch. Upgrading avoids that juggling.

What not to do

  • Don't stop at package.json. A ^1.7.0 range can still lock 1.7.6. Check the lockfile and what your build installs.
  • Don't upgrade one server and leave the others. Servers sharing auth storage, and every OAuth Proxy environment, go together.
  • Don't treat disableSignUp or disableImplicitLinking as the fix. The advisories say each one alone is not enough.
  • Don't sign users out by deleting rows from your session table. Use the documented revoke calls above.
  • Don't assume you're safe because you never set these options. Several affected settings are the defaults.

AI prompt

Check package.json and the lockfile (package-lock.json, pnpm-lock.yaml,
yarn.lock or bun.lock) for the installed "better-auth" version and any
"@better-auth/*" packages. If better-auth is below 1.7.7, propose upgrading
better-auth and every @better-auth/* package I list to the same latest 1.7.x
version. If I'm on 1.6.x or older, stop and summarize the better-auth 1.7
upgrade guide steps that apply to my config instead.
Then search my auth config (not node_modules) for socialProviders,
genericOAuth, magicLink, oAuthProxy, deviceAuthorization, storeStateStrategy,
storeToken, storeIdentifier and rateLimit, and tell me which of
GHSA-965c-763c-88jm, GHSA-r4xp-prcw-77qf, GHSA-q84f-53jg-9ppm and
GHSA-44jh-23m7-hpcf my config matched before the upgrade.
Wait for my OK before changing anything. Don't delete session rows directly.

How to verify

  1. npm ls better-auth (or your package manager's equivalent) shows 1.7.7 or newer, and the lockfile matches. Verified on throwaway projects.
  2. npm audit no longer lists any of the four advisories. Verified on a throwaway npm project.
  3. Every deployment that shares your auth storage or proxy flow runs the new build.
  4. Sign in with a test account on two browsers, call revokeOtherSessions() from one, and confirm the other is signed out. Verified on a throwaway local project.
  5. If you use Magic Link: a link sent before the upgrade fails, and a freshly requested one works.
  6. If you use device login: your approval page shows the client and requested scopes before the approve button works.

Pre-launch checklist · Next.js security update check · Poisoned dependency guide · Secret key in frontend bundle

Your turn

Which features did your config match: Magic Link, OAuth Proxy, device login, or none? Tell us what the search in step 4 turned up. We'll keep this page current as better-auth updates these advisories.

This guide is educational and based on the vendor's public docs as of the review date. Platforms change: check the linked sources. Not a security audit. Disclaimer.

The BuiltSafely weekly

One email a week. What changed, and the one fix that matters.

Platform changes that affect the security of AI-built apps, checked against official docs. Free. Unsubscribe any time.