Delete verification challenge after use (passkey) — remove one‑time tokens after successful verification #2351

Closed
opened 2026-03-13 09:46:01 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @Ridhim-RR on GitHub (Nov 22, 2025).

The passkey plugin persists WebAuthn challenge state in the verification store and sets a signed cookie containing the generated challenge identifier. Currently the verification rows are not consistently removed after a successful registration/authentication flow. This leaves one‑time tokens present in the DB and increases replay risk if an attacker obtains the identifier or DB snapshot.

This issue requests ensuring verification values are deleted immediately after successful verification and the challenge cookie is cleared.

Why
Current flow: generate -> store verification row (identifier + value) -> set signed cookie with identifier -> verify -> [sometimes not deleted].
Problem: verification rows can remain after success, enabling reuse/replay and causing test brittleness.
Benefit: deletion is low effort and removes replay risk; high security gain.

Originally created by @Ridhim-RR on GitHub (Nov 22, 2025). The passkey plugin persists WebAuthn challenge state in the [verification](vscode-file://vscode-app/snap/code/212/usr/share/code/resources/app/out/vs/code/electron-browser/workbench/workbench.html) store and sets a signed cookie containing the generated challenge identifier. Currently the verification rows are not consistently removed after a successful registration/authentication flow. This leaves one‑time tokens present in the DB and increases replay risk if an attacker obtains the identifier or DB snapshot. This issue requests ensuring verification values are deleted immediately after successful verification and the challenge cookie is cleared. **Why** Current flow: generate -> store verification row (identifier + value) -> set signed cookie with identifier -> verify -> [sometimes not deleted]. Problem: verification rows can remain after success, enabling reuse/replay and causing test brittleness. Benefit: deletion is low effort and removes replay risk; high security gain.
GiteaMirror added the security label 2026-03-13 09:46:01 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 22, 2025):

tag @better-auth-agent if you would like an answer from the Agent

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 22, 2025): tag @better-auth-agent if you would like an answer from the Agent <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@Ridhim-RR commented on GitHub (Nov 22, 2025):

@better-auth-agent Could you please check this issue?

@Ridhim-RR commented on GitHub (Nov 22, 2025): @better-auth-agent Could you please check this issue?
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 22, 2025):

Hi there, thanks for raising this issue. I understand you’re seeing that after a successful passkey registration or authentication, the one‑time challenge token remains in the verification store and the associated cookie isn’t cleared, which can potentially lead to replay risks.

Our next steps are to review the part of our passkey plugin that handles verification—specifically, ensuring that immediately after a successful challenge validation the verification record is removed and the challenge cookie is cleared. We’ll need to confirm that the deletion process isn’t being bypassed by any asynchronous race conditions or caching behaviors.

Could you please let us know if there are any specific conditions or environments where you noticed this behavior? That additional context would be really helpful. Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 22, 2025): Hi there, thanks for raising this issue. I understand you’re seeing that after a successful passkey registration or authentication, the one‑time challenge token remains in the verification store and the associated cookie isn’t cleared, which can potentially lead to replay risks. Our next steps are to review the part of our passkey plugin that handles verification—specifically, ensuring that immediately after a successful challenge validation the verification record is removed and the challenge cookie is cleared. We’ll need to confirm that the deletion process isn’t being bypassed by any asynchronous race conditions or caching behaviors. Could you please let us know if there are any specific conditions or environments where you noticed this behavior? That additional context would be really helpful. Tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 22, 2025):

To prevent replay risk and improve security, verification challenges (one-time tokens) and their cookies should be deleted immediately after successful passkey registration or authentication. The passkey plugin currently stores the challenge in the verification table and sets a signed cookie, but does not consistently remove them after use, leaving tokens in the database and cookies in the browser, which can be exploited if leaked or if the DB is compromised [source].

How to fix:
After successful verification (in both /passkey/verify-registration and /passkey/verify-authentication endpoints), call the internal adapter’s deleteVerificationByIdentifier(challengeId) to remove the challenge from the DB, and clear the challenge cookie by setting it again with maxAge: 0 (e.g., ctx.setCookie(webAuthnCookie.name, "", { ...webAuthnCookie.attributes, maxAge: 0 })). This ensures the challenge is single-use and cannot be replayed [source].

This matches best practices already used for OTP and magic link flows in better-auth, and will help prevent test brittleness and security issues from lingering tokens [source].

If you want to cover edge cases (like failed verification or timeouts), consider also running periodic cleanup of expired challenges, but immediate deletion after success is the most important step.

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

@dosubot[bot] commented on GitHub (Nov 22, 2025): <!-- Answer --> To prevent replay risk and improve security, verification challenges (one-time tokens) and their cookies should be deleted immediately after successful passkey registration or authentication. The passkey plugin currently stores the challenge in the verification table and sets a signed cookie, but does not consistently remove them after use, leaving tokens in the database and cookies in the browser, which can be exploited if leaked or if the DB is compromised [[source](https://github.com/better-auth/better-auth/blob/44308bf20d431bd943a92a2ed4204a6b7d5a4182/packages/passkey/src/index.ts#L40-L691)]. **How to fix:** After successful verification (in both `/passkey/verify-registration` and `/passkey/verify-authentication` endpoints), call the internal adapter’s `deleteVerificationByIdentifier(challengeId)` to remove the challenge from the DB, and clear the challenge cookie by setting it again with `maxAge: 0` (e.g., `ctx.setCookie(webAuthnCookie.name, "", { ...webAuthnCookie.attributes, maxAge: 0 })`). This ensures the challenge is single-use and cannot be replayed [[source](https://github.com/better-auth/better-auth/blob/44308bf20d431bd943a92a2ed4204a6b7d5a4182/packages/better-auth/src/db/internal-adapter.ts#L911-L980)]. This matches best practices already used for OTP and magic link flows in better-auth, and will help prevent test brittleness and security issues from lingering tokens [[source](https://github.com/better-auth/better-auth/issues/1085)]. If you want to cover edge cases (like failed verification or timeouts), consider also running periodic cleanup of expired challenges, but immediate deletion after success is the most important step. <!-- 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/3a914543-a534-4432-bbd4-5d0419a006e3?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/3a914543-a534-4432-bbd4-5d0419a006e3?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/3a914543-a534-4432-bbd4-5d0419a006e3?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/3a914543-a534-4432-bbd4-5d0419a006e3?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/3a914543-a534-4432-bbd4-5d0419a006e3?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/3a914543-a534-4432-bbd4-5d0419a006e3?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/3a914543-a534-4432-bbd4-5d0419a006e3?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=To%20prevent%20replay%20risk%20and%20improve%20security%2C%20verification%20challenges%20%28one-time%20tokens%29%20and%20their%20cookies%20should%20be%20deleted%20immediately%20after%20successful%20passkey%20registration%20or%20authentication.%20The%20passkey%20plugin%20currently%20stores%20the%20challenge%20in%20the%20verification%20table%20and%20sets%20a%20signed%20cookie%2C%20but%20does%20not%20consistently%20remove%20them%20after%20use%2C%20leaving%20tokens%20in%20the%20database%20and%20cookies%20in%20the%20browser%2C%20which%20can%20be%20exploited%20if%20leaked%20or%20if%20the%20DB%20is%20compromised%20%5B%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/blob/44308bf20d431bd943a92a2ed4204a6b7d5a4182/packages/passkey/src/index.ts%23L40-L691%29%5D.%0A%0A%2A%2AHow%20to%20fix%3A%2A%2A%20%20%0AAfter%20successful%20verification%20%28in%20both%20%60/passkey/verify-registration%60%20and%20%60/passkey/verify-authentication%60%20endpoints%29%2C%20call%20the%20internal%20adapter%E2%80%99s%20%60deleteVerificationByIdentifier%28challengeId%29%60%20to%20remove%20the%20challenge%20from%20the%20DB%2C%20and%20clear%20the%20challenge%20cookie%20by%20setting%20it%20again%20with%20%60maxAge%3A%200%60%20%28e.g.%2C%20%60ctx.setCookie%28webAuthnCookie.name%2C%20%22%22%2C%20%7B%20...webAuthnCookie.attributes%2C%20maxAge%3A%200%20%7D%29%60%29.%20This%20ensures%20the%20challenge%20is%20single-use%20and%20cannot%20be%20replayed%20%5B%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/blob/44308bf20d431bd943a92a2ed4204a6b7d5a4182/packages/better-auth/src/db/internal-adapter.ts%23L911-L980%29%5D.%0A%0AThis%20matches%20best%20practices%20already%20used%20for%20OTP%20and%20magic%20link%20flows%20in%20better-auth%2C%20and%20will%20help%20prevent%20test%20brittleness%20and%20security%20issues%20from%20lingering%20tokens%20%5B%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/1085%29%5D.%0A%0AIf%20you%20want%20to%20cover%20edge%20cases%20%28like%20failed%20verification%20or%20timeouts%29%2C%20consider%20also%20running%20periodic%20cleanup%20of%20expired%20challenges%2C%20but%20immediate%20deletion%20after%20success%20is%20the%20most%20important%20step.)&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/6192)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2351