Can't unlink Google account if it's the only OAuth provider, even with other login methods #1957

Closed
opened 2026-03-13 09:16:17 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @issam-seghir on GitHub (Sep 18, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

When trying to unlink a Google account, "Better Auth" throws:

Response: [Error [APIError]: You can't unlink your last account] {
  status: 'BAD_REQUEST',
  body: [Object],
  headers: {},
  statusCode: 400
}
 POST /api/auth/unlink-account 400 in 872ms

This logic is incorrect because the user does have other authentication methods (e.g. magic link, passkeys), but "Better Auth" only checks the "accounts" list.

Steps to Reproduce

  1. Create a user with:
    • Magic link (default login method)
    • Passkeys registered
    • Google OAuth account linked
  2. Try to unlink the Google account:
   POST /api/auth/unlink-account
  1. Observe the error:

    400 BAD_REQUEST
    You can't unlink your last account
    

Current vs. Expected behavior

Expected Behavior

Unlinking should be allowed as long as the user has at least one verified authentication method:

  • emailVerified
  • phoneVerified
  • passkeys list not empty
  • accounts list not empty

The check should not rely only on the "accounts" table.

Actual Behavior

  • "Better Auth" checks only the "accounts" list.
  • If Google OAuth is the only entry in "accounts", unlinking is blocked — even if the user has email / passkeys.

What version of Better Auth are you using?

1.3.10

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Pro",
    "release": "10.0.26100",
    "cpuCount": 12,
    "cpuModel": "AMD Ryzen 5 PRO 4650G with Radeon Graphics",
    "totalMemory": "31.30 GB",
    "freeMemory": "4.36 GB"
  },
  "node": {
    "version": "v22.17.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.2"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "15.5.2"
    },
    {
      "name": "react",
      "version": "19.1.1"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "@prisma/client",
      "version": "^6.15.0"
    }
  ],
  "betterAuth": {
    "version": "^1.3.10",
    "config": null
  }
}

Which area(s) are affected? (Select all that apply)

Client, Backend, Package

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

Suggested Fix

Update unlink logic to check for all auth methods, not just accounts:

  • If user has verified email → allow unlink
  • If user has passkeys → allow unlink
  • If user has other providers in "accounts" → allow unlink
  • Only block unlink if truly no login method would remain
Originally created by @issam-seghir on GitHub (Sep 18, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce When trying to unlink a Google account, "Better Auth" throws: ```bash Response: [Error [APIError]: You can't unlink your last account] { status: 'BAD_REQUEST', body: [Object], headers: {}, statusCode: 400 } POST /api/auth/unlink-account 400 in 872ms ```` This logic is incorrect because the user **does have other authentication methods** (e.g. magic link, passkeys), but "Better Auth" only checks the "accounts" list. ### Steps to Reproduce 1. Create a user with: - Magic link (default login method) - Passkeys registered - Google OAuth account linked 2. Try to unlink the Google account: ```http POST /api/auth/unlink-account ```` 3. Observe the error: ``` 400 BAD_REQUEST You can't unlink your last account ``` ### Current vs. Expected behavior ### Expected Behavior Unlinking should be allowed **as long as the user has at least one verified authentication method**: * `emailVerified` * `phoneVerified` * `passkeys` list not empty * `accounts` list not empty The check should not rely only on the "accounts" table. ### Actual Behavior * "Better Auth" checks only the "accounts" list. * If Google OAuth is the only entry in "accounts", unlinking is blocked — even if the user has email / passkeys. ### What version of Better Auth are you using? 1.3.10 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Pro", "release": "10.0.26100", "cpuCount": 12, "cpuModel": "AMD Ryzen 5 PRO 4650G with Radeon Graphics", "totalMemory": "31.30 GB", "freeMemory": "4.36 GB" }, "node": { "version": "v22.17.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.2" }, "frameworks": [ { "name": "next", "version": "15.5.2" }, { "name": "react", "version": "19.1.1" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "@prisma/client", "version": "^6.15.0" } ], "betterAuth": { "version": "^1.3.10", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Client, Backend, Package ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context ### Suggested Fix Update unlink logic to check for **all auth methods**, not just `accounts`: * If user has verified email → allow unlink * If user has passkeys → allow unlink * If user has other providers in "accounts" → allow unlink * Only block unlink if truly **no login method would remain**
GiteaMirror added the social-provider label 2026-03-13 09:16:17 -05:00
Author
Owner

@issam-seghir commented on GitHub (Sep 18, 2025):

@dosubot what do you think about that ?

@issam-seghir commented on GitHub (Sep 18, 2025): @dosubot what do you think about that ?
Author
Owner

@dosubot[bot] commented on GitHub (Sep 18, 2025):

You're absolutely right—the current unlink logic only checks the OAuth "accounts" list and ignores other valid login methods like passkeys and verified email. The codebase tracks these authentication methods separately, so it's feasible to expand the unlink logic to check for all supported login methods before blocking unlinking. This would align with best practices and provide a better user experience, ensuring users aren't locked out if they still have a way to log in (for example, via passkey or magic link) see code details.

No recent updates or PRs address this, so your suggested fix—to allow unlinking as long as at least one verified authentication method remains—is both reasonable and implementable within the current architecture. The logic should check for: at least one OAuth account, emailVerified, phoneVerified, or a non-empty passkeys list. This would prevent accidental lockouts and make the unlink flow much more robust.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Sep 18, 2025): <!-- Answer --> You're absolutely right—the current unlink logic only checks the OAuth "accounts" list and ignores other valid login methods like passkeys and verified email. The codebase tracks these authentication methods separately, so it's feasible to expand the unlink logic to check for all supported login methods before blocking unlinking. This would align with best practices and provide a better user experience, ensuring users aren't locked out if they still have a way to log in (for example, via passkey or magic link) [see code details](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/passkey/index.ts). No recent updates or PRs address this, so your suggested fix—to allow unlinking as long as at least one verified authentication method remains—is both reasonable and implementable within the current architecture. The logic should check for: at least one OAuth account, emailVerified, phoneVerified, or a non-empty passkeys list. This would prevent accidental lockouts and make the unlink flow much more robust. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/b6b92d6e-378a-4215-9ec2-c71c183ddbce?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/b6b92d6e-378a-4215-9ec2-c71c183ddbce?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/b6b92d6e-378a-4215-9ec2-c71c183ddbce?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/b6b92d6e-378a-4215-9ec2-c71c183ddbce?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/b6b92d6e-378a-4215-9ec2-c71c183ddbce?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/b6b92d6e-378a-4215-9ec2-c71c183ddbce?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/b6b92d6e-378a-4215-9ec2-c71c183ddbce?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4742)
Author
Owner

@Bekacru commented on GitHub (Sep 18, 2025):

You can bypass this by setting account.accountLinking.allowUnlinkingAll to true in your auth config

@Bekacru commented on GitHub (Sep 18, 2025): You can bypass this by setting `account.accountLinking.allowUnlinkingAll` to `true` in your auth config
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1957