[GH-ISSUE #6807] TwoFactor delete fails with Prisma when userId is not unique field #19267

Closed
opened 2026-04-15 18:08:15 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @guilhermejansen on GitHub (Dec 16, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6807

When disabling two-factor authentication via twoFactor.disable(), the Prisma adapter throws an error because it tries to delete the TwoFactor record using userId in the where clause, but Prisma requires a unique field for delete operations.

Error

Error [PrismaClientValidationError]:
Invalid `db[model].delete()` invocation

→ await db[model].delete({
    where: {
      userId: "xxxxx",
  ?   id?: String,
  ?   AND?: TwoFactorWhereInput | TwoFactorWhereInput[],
      ...
    }
  })

Argument `where` of type TwoFactorWhereUniqueInput needs at least one of `id` arguments.

Environment

  • better-auth: 1.4.5
  • @prisma/client: 6.19.0
  • PostgreSQL
  • Bun runtime

My TwoFactor schema

model TwoFactor {
  id          String   @id @default(dbgenerated("gen_random_uuid()"))
  secret      String
  backupCodes String
  userId      String
  createdAt   DateTime @default(now())
  user        User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([secret])
  @@index([userId])
  @@map("twoFactor")
}

Reproduction

  1. Set up better-auth with Prisma adapter
  2. Enable 2FA for a user
  3. Try to disable 2FA
  4. Get the validation error

Workaround

Adding @unique to userId fixes it:

userId String @unique

But this shouldn't be required since users can only have one TwoFactor record anyway. The adapter should either query by userId first to get the id, then delete by id.

Originally created by @guilhermejansen on GitHub (Dec 16, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6807 When disabling two-factor authentication via `twoFactor.disable()`, the Prisma adapter throws an error because it tries to delete the TwoFactor record using `userId` in the where clause, but Prisma requires a unique field for delete operations. ### Error ``` Error [PrismaClientValidationError]: Invalid `db[model].delete()` invocation → await db[model].delete({ where: { userId: "xxxxx", ? id?: String, ? AND?: TwoFactorWhereInput | TwoFactorWhereInput[], ... } }) Argument `where` of type TwoFactorWhereUniqueInput needs at least one of `id` arguments. ``` ### Environment - better-auth: 1.4.5 - @prisma/client: 6.19.0 - PostgreSQL - Bun runtime ### My TwoFactor schema ```prisma model TwoFactor { id String @id @default(dbgenerated("gen_random_uuid()")) secret String backupCodes String userId String createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([secret]) @@index([userId]) @@map("twoFactor") } ``` ### Reproduction 1. Set up better-auth with Prisma adapter 2. Enable 2FA for a user 3. Try to disable 2FA 4. Get the validation error ### Workaround Adding `@unique` to userId fixes it: ```prisma userId String @unique ``` But this shouldn't be required since users can only have one TwoFactor record anyway. The adapter should either query by userId first to get the id, then delete by id.
GiteaMirror added the lockedbug labels 2026-04-15 18:08:15 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Dec 16, 2025):

Hi @guilhermejansen! I'm Dosu and I’m helping the better-auth team.

The Prisma delete error happens because the adapter tries to delete a TwoFactor record using only userId, which isn't unique in your schema. Prisma requires a unique field (like id) for delete operations, so this fails with a validation error (source).

The current better-auth implementation for twoFactor.disable() calls the adapter's delete method with just userId (source), which leads to this error if userId isn't unique.

To fix this without changing your schema, you can patch the adapter logic to first query for the record by userId, get its id, and then delete by id. For example:

// Pseudocode for a custom adapter or patch
const record = await db.twoFactor.findFirst({ where: { userId } });
if (record) {
  await db.twoFactor.delete({ where: { id: record.id } });
}

Alternatively, you could use deleteMany if you expect only one record per user:

await db.twoFactor.deleteMany({ where: { userId } });

But this won't throw if multiple records exist, so it's safer to enforce uniqueness or use the two-step approach above (discussion, adapter code).

There are several related issues and discussions confirming this pattern and workaround (see here, here). No recent PRs have fixed this in the core package, so a local patch or custom adapter is the best solution for now.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3662304479 --> @dosubot[bot] commented on GitHub (Dec 16, 2025): <!-- Greeting --> Hi @guilhermejansen! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The Prisma delete error happens because the adapter tries to delete a TwoFactor record using only userId, which isn't unique in your schema. Prisma requires a unique field (like id) for delete operations, so this fails with a validation error ([source](https://github.com/better-auth/better-auth/blob/1c671650504b31626c7e12082b73b8e7832b7a5d/packages/better-auth/src/adapters/prisma-adapter/prisma-adapter.ts#L186-L394)). The current better-auth implementation for twoFactor.disable() calls the adapter's delete method with just userId ([source](https://github.com/better-auth/better-auth/blob/1c671650504b31626c7e12082b73b8e7832b7a5d/packages/better-auth/src/plugins/two-factor/index.ts#L1-L266)), which leads to this error if userId isn't unique. To fix this without changing your schema, you can patch the adapter logic to first query for the record by userId, get its id, and then delete by id. For example: ```ts // Pseudocode for a custom adapter or patch const record = await db.twoFactor.findFirst({ where: { userId } }); if (record) { await db.twoFactor.delete({ where: { id: record.id } }); } ``` Alternatively, you could use deleteMany if you expect only one record per user: ```ts await db.twoFactor.deleteMany({ where: { userId } }); ``` But this won't throw if multiple records exist, so it's safer to enforce uniqueness or use the two-step approach above ([discussion](https://github.com/better-auth/better-auth/issues/2474), [adapter code](https://github.com/better-auth/better-auth/blob/1c671650504b31626c7e12082b73b8e7832b7a5d/packages/better-auth/src/adapters/prisma-adapter/prisma-adapter.ts#L186-L394)). There are several related issues and discussions confirming this pattern and workaround ([see here](https://github.com/better-auth/better-auth/issues/2474), [here](https://github.com/better-auth/better-auth/issues/5037)). No recent PRs have fixed this in the core package, so a local patch or custom adapter is the best solution for now. <!-- 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/13b111cd-ccd5-41c8-9237-f19fe0187496?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/13b111cd-ccd5-41c8-9237-f19fe0187496?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/13b111cd-ccd5-41c8-9237-f19fe0187496?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/13b111cd-ccd5-41c8-9237-f19fe0187496?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/13b111cd-ccd5-41c8-9237-f19fe0187496?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/13b111cd-ccd5-41c8-9237-f19fe0187496?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/13b111cd-ccd5-41c8-9237-f19fe0187496?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20Prisma%20delete%20error%20happens%20because%20the%20adapter%20tries%20to%20delete%20a%20TwoFactor%20record%20using%20only%20userId%2C%20which%20isn%27t%20unique%20in%20your%20schema.%20Prisma%20requires%20a%20unique%20field%20%28like%20id%29%20for%20delete%20operations%2C%20so%20this%20fails%20with%20a%20validation%20error%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/blob/1c671650504b31626c7e12082b73b8e7832b7a5d/packages/better-auth/src/adapters/prisma-adapter/prisma-adapter.ts%23L186-L394%29%29.%0A%0AThe%20current%20better-auth%20implementation%20for%20twoFactor.disable%28%29%20calls%20the%20adapter%27s%20delete%20method%20with%20just%20userId%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/blob/1c671650504b31626c7e12082b73b8e7832b7a5d/packages/better-auth/src/plugins/two-factor/index.ts%23L1-L266%29%29%2C%20which%20leads%20to%20this%20error%20if%20userId%20isn%27t%20unique.%0A%0ATo%20fix%20this%20without%20changing%20your%20schema%2C%20you%20can%20patch%20the%20adapter%20logic%20to%20first%20query%20for%20the%20record%20by%20userId%2C%20get%20its%20id%2C%20and%20then%20delete%20by%20id.%20For%20example%3A%0A%0A%60%60%60ts%0A//%20Pseudocode%20for%20a%20custom%20adapter%20or%20patch%0Aconst%20record%20%3D%20await%20db.twoFactor.findFirst%28%7B%20where%3A%20%7B%20userId%20%7D%20%7D%29%3B%0Aif%20%28record%29%20%7B%0A%20%20await%20db.twoFactor.delete%28%7B%20where%3A%20%7B%20id%3A%20record.id%20%7D%20%7D%29%3B%0A%7D%0A%60%60%60%0A%0AAlternatively%2C%20you%20could%20use%20deleteMany%20if%20you%20expect%20only%20one%20record%20per%20user%3A%0A%0A%60%60%60ts%0Aawait%20db.twoFactor.deleteMany%28%7B%20where%3A%20%7B%20userId%20%7D%20%7D%29%3B%0A%60%60%60%0A%0ABut%20this%20won%27t%20throw%20if%20multiple%20records%20exist%2C%20so%20it%27s%20safer%20to%20enforce%20uniqueness%20or%20use%20the%20two-step%20approach%20above%20%28%5Bdiscussion%5D%28https%3A//github.com/better-auth/better-auth/issues/2474%29%2C%20%5Badapter%20code%5D%28https%3A//github.com/better-auth/better-auth/blob/1c671650504b31626c7e12082b73b8e7832b7a5d/packages/better-auth/src/adapters/prisma-adapter/prisma-adapter.ts%23L186-L394%29%29.%0A%0AThere%20are%20several%20related%20issues%20and%20discussions%20confirming%20this%20pattern%20and%20workaround%20%28%5Bsee%20here%5D%28https%3A//github.com/better-auth/better-auth/issues/2474%29%2C%20%5Bhere%5D%28https%3A//github.com/better-auth/better-auth/issues/5037%29%29.%20No%20recent%20PRs%20have%20fixed%20this%20in%20the%20core%20package%2C%20so%20a%20local%20patch%20or%20custom%20adapter%20is%20the%20best%20solution%20for%20now.)&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/6807)
Author
Owner

@guilhermejansen commented on GitHub (Dec 16, 2025):

@dosu Thanks for the context and linking related issues.

The @unique workaround works fine for my use case since users can only have one TwoFactor record anyway.

However, it seems like the adapter should handle this internally - either by using findFirst + delete or by using deleteMany. Is there any plan to fix this in the core package? I can submit a PR if needed.

<!-- gh-comment-id:3662311089 --> @guilhermejansen commented on GitHub (Dec 16, 2025): @dosu Thanks for the context and linking related issues. The `@unique` workaround works fine for my use case since users can only have one TwoFactor record anyway. However, it seems like the adapter should handle this internally - either by using `findFirst` + `delete` or by using `deleteMany`. Is there any plan to fix this in the core package? I can submit a PR if needed.
Author
Owner

@dosubot[bot] commented on GitHub (Dec 16, 2025):

There is an open pull request (#6425) that proposes a fix for this exact issue in the Prisma adapter. The PR updates the delete logic so that the id field is always at the root of the where clause for delete operations, which resolves the Prisma error when deleting by non-unique fields like userId. This change is not yet merged, but it directly addresses the problem you described.

Since a fix is already proposed and under review, you likely don't need to submit a new PR unless you have additional improvements or feedback. If this is urgent for your project, you can monitor or comment on the PR to express support or share your use case. Until the fix is merged, your @unique workaround or a local patch remains the best option.

References:
#6425 (open PR)
Related issue #2474

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3662320324 --> @dosubot[bot] commented on GitHub (Dec 16, 2025): <!-- Answer --> There is an open pull request ([#6425](https://github.com/better-auth/better-auth/pull/6425)) that proposes a fix for this exact issue in the Prisma adapter. The PR updates the delete logic so that the id field is always at the root of the where clause for delete operations, which resolves the Prisma error when deleting by non-unique fields like userId. This change is not yet merged, but it directly addresses the problem you described. Since a fix is already proposed and under review, you likely don't need to submit a new PR unless you have additional improvements or feedback. If this is urgent for your project, you can monitor or comment on the PR to express support or share your use case. Until the fix is merged, your @unique workaround or a local patch remains the best option. References: [#6425 (open PR)](https://github.com/better-auth/better-auth/pull/6425) [Related issue #2474](https://github.com/better-auth/better-auth/issues/2474) <!-- 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/3403592f-4526-4aef-9e59-f1e3d1e2336c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/3403592f-4526-4aef-9e59-f1e3d1e2336c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/3403592f-4526-4aef-9e59-f1e3d1e2336c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/3403592f-4526-4aef-9e59-f1e3d1e2336c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/3403592f-4526-4aef-9e59-f1e3d1e2336c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/3403592f-4526-4aef-9e59-f1e3d1e2336c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/3403592f-4526-4aef-9e59-f1e3d1e2336c?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=There%20is%20an%20open%20pull%20request%20%28%5B%236425%5D%28https%3A//github.com/better-auth/better-auth/pull/6425%29%29%20that%20proposes%20a%20fix%20for%20this%20exact%20issue%20in%20the%20Prisma%20adapter.%20The%20PR%20updates%20the%20delete%20logic%20so%20that%20the%20id%20field%20is%20always%20at%20the%20root%20of%20the%20where%20clause%20for%20delete%20operations%2C%20which%20resolves%20the%20Prisma%20error%20when%20deleting%20by%20non-unique%20fields%20like%20userId.%20This%20change%20is%20not%20yet%20merged%2C%20but%20it%20directly%20addresses%20the%20problem%20you%20described.%0A%0ASince%20a%20fix%20is%20already%20proposed%20and%20under%20review%2C%20you%20likely%20don%27t%20need%20to%20submit%20a%20new%20PR%20unless%20you%20have%20additional%20improvements%20or%20feedback.%20If%20this%20is%20urgent%20for%20your%20project%2C%20you%20can%20monitor%20or%20comment%20on%20the%20PR%20to%20express%20support%20or%20share%20your%20use%20case.%20Until%20the%20fix%20is%20merged%2C%20your%20%40unique%20workaround%20or%20a%20local%20patch%20remains%20the%20best%20option.%20%0A%0AReferences%3A%20%20%0A%5B%236425%20%28open%20PR%29%5D%28https%3A//github.com/better-auth/better-auth/pull/6425%29%20%20%0A%5BRelated%20issue%20%232474%5D%28https%3A//github.com/better-auth/better-auth/issues/2474%29)&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/6807)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19267