Why does signIn.passkey require an email when signing in ? #1952

Closed
opened 2026-03-13 09:15:21 -05:00 by GiteaMirror · 14 comments
Owner

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

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Hi , I'm using the passkey plugin in my app
Here's what I did:

  • Signed into my app
  • Added a passkey (Windows Hello / PIN)
  • Tried to sign in with a passkey

When I checked the docs, I saw that email is required:

const { data, error } = await authClient.signIn.passkey({
  email: "example@gmail.com", // required
  autoFill: true,
});

This feels a bit strange to me 🤔 because on other platforms (like GitHub), I can log in directly with a passkey without typing an email first.

My questions are:

  • Why is email required here?
  • Is this a design choice specific to Better Auth?
  • Or is it a limitation of the passkey plugin itself (not passkeys in general)?

Current vs. Expected behavior

expected an optional email input

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": "6.21 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)

Documentation, Package, Client, Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
     ....
     passkey({
            rpID: getRpId(),
            rpName: siteConfig.name,
            origin: ENV.NEXT_PUBLIC_APP_URL,
            authenticatorSelection: {
                residentKey: isProd ? "required" : "preferred",
                userVerification: isProd ? "required" : "preferred",
                authenticatorAttachment: "platform",
            },
        }),
});

Additional context

No response

Originally created by @issam-seghir on GitHub (Sep 17, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Hi , I'm using the passkey plugin in my app Here's what I did: - Signed into my app - Added a passkey (Windows Hello / PIN) - Tried to sign in with a passkey When I checked the docs, I saw that email is required: ```ts const { data, error } = await authClient.signIn.passkey({ email: "example@gmail.com", // required autoFill: true, }); ``` This feels a bit strange to me 🤔 because on other platforms (like GitHub), I can log in directly with a passkey without typing an email first. My questions are: - Why is email required here? - Is this a design choice specific to Better Auth? - Or is it a limitation of the passkey plugin itself (not passkeys in general)? ### Current vs. Expected behavior expected an optional email input ### 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": "6.21 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) Documentation, Package, Client, Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ .... passkey({ rpID: getRpId(), rpName: siteConfig.name, origin: ENV.NEXT_PUBLIC_APP_URL, authenticatorSelection: { residentKey: isProd ? "required" : "preferred", userVerification: isProd ? "required" : "preferred", authenticatorAttachment: "platform", }, }), }); ``` ### Additional context _No response_
GiteaMirror added the question label 2026-03-13 09:15:21 -05:00
Author
Owner

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

The reason signIn.passkey requires an email in Better Auth is to identify the user and fetch their registered passkeys before generating authentication options. This is a design choice in the current implementation—not a technical limitation of passkeys or WebAuthn. The backend endpoint actually marks email as optional, but the client usage enforces it for user lookup. Platforms like GitHub support true discoverable credential flows, where the authenticator supplies the user identity and no email is needed upfront. Better Auth’s passkey plugin does support resident credentials (discoverable credentials) in its config, but the sign-in flow still requires an email to map the credential to a user record before authentication. The verification step itself does not require email; it matches the credentialID to a stored passkey and userId, then creates a session for that user. There’s currently no configuration to make the email optional or to support credential-only sign-in in Better Auth’s passkey flow.
Source: better-auth passkey plugin code

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 17, 2025): <!-- Answer --> The reason signIn.passkey requires an email in Better Auth is to identify the user and fetch their registered passkeys before generating authentication options. This is a design choice in the current implementation—not a technical limitation of passkeys or WebAuthn. The backend endpoint actually marks email as optional, but the client usage enforces it for user lookup. Platforms like GitHub support true discoverable credential flows, where the authenticator supplies the user identity and no email is needed upfront. Better Auth’s passkey plugin does support resident credentials (discoverable credentials) in its config, but the sign-in flow still requires an email to map the credential to a user record before authentication. The verification step itself does not require email; it matches the credentialID to a stored passkey and userId, then creates a session for that user. There’s currently no configuration to make the email optional or to support credential-only sign-in in Better Auth’s passkey flow. [Source: better-auth passkey plugin code](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/passkey/index.ts) <!-- 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/4bce11f9-acb8-4077-8fbc-d0195a574296?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/4bce11f9-acb8-4077-8fbc-d0195a574296?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/4bce11f9-acb8-4077-8fbc-d0195a574296?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/4bce11f9-acb8-4077-8fbc-d0195a574296?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/4bce11f9-acb8-4077-8fbc-d0195a574296?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/4bce11f9-acb8-4077-8fbc-d0195a574296?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/4bce11f9-acb8-4077-8fbc-d0195a574296?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/4731)
Author
Owner

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

@dosubot so this is actually a mistake on client side ? the email should be optional right ?

@issam-seghir commented on GitHub (Sep 17, 2025): @dosubot so this is actually a mistake on client side ? the email should be optional right ?
Author
Owner

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

Yes, that's correct—the email requirement in signIn.passkey is a client-side design choice, not a technical necessity. The backend actually supports credential-only (discoverable) passkey sign-in and marks email as optional, so the client could allow sign-in without requiring an email if it was updated to do so. Making email optional would enable flows like GitHub's, where you can sign in directly with a passkey and let the authenticator provide the user identity.
Source: better-auth passkey plugin code

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 17, 2025): <!-- Answer --> Yes, that's correct—the email requirement in signIn.passkey is a client-side design choice, not a technical necessity. The backend actually supports credential-only (discoverable) passkey sign-in and marks email as optional, so the client could allow sign-in without requiring an email if it was updated to do so. Making email optional would enable flows like GitHub's, where you can sign in directly with a passkey and let the authenticator provide the user identity. [Source: better-auth passkey plugin code](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/passkey/index.ts) <!-- 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/54beb8c1-8362-4a5b-abdd-ec052d511c75?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/54beb8c1-8362-4a5b-abdd-ec052d511c75?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/54beb8c1-8362-4a5b-abdd-ec052d511c75?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/54beb8c1-8362-4a5b-abdd-ec052d511c75?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/54beb8c1-8362-4a5b-abdd-ec052d511c75?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/54beb8c1-8362-4a5b-abdd-ec052d511c75?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/54beb8c1-8362-4a5b-abdd-ec052d511c75?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/4731)
Author
Owner

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

@dosubot this is should be labeled as an Issus not a question then

@issam-seghir commented on GitHub (Sep 17, 2025): @dosubot this is should be labeled as an Issus not a question then
Author
Owner

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

Agreed—this should be labeled as an issue. The backend supports passkey sign-in without requiring email, but the client enforces it unnecessarily, which is a design oversight rather than a technical limitation. There’s no official documentation justifying the client-side requirement, and related GitHub issues highlight missing or inconsistent props in the passkey sign-in flow, supporting your point that this is a UX/API bug rather than a question. Making email optional would align Better Auth with platforms like GitHub and improve the experience for discoverable credentials.
Relevant issue #2911
Related discussion on email requirements #2172

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 17, 2025): <!-- Answer --> Agreed—this should be labeled as an issue. The backend supports passkey sign-in without requiring email, but the client enforces it unnecessarily, which is a design oversight rather than a technical limitation. There’s no official documentation justifying the client-side requirement, and related GitHub issues highlight missing or inconsistent props in the passkey sign-in flow, supporting your point that this is a UX/API bug rather than a question. Making email optional would align Better Auth with platforms like GitHub and improve the experience for discoverable credentials. [Relevant issue #2911](https://github.com/better-auth/better-auth/issues/2911) [Related discussion on email requirements #2172](https://github.com/better-auth/better-auth/issues/2172) <!-- 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/b9d8a95f-2cc8-4989-bf6c-ee6361e08ca0?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/b9d8a95f-2cc8-4989-bf6c-ee6361e08ca0?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/b9d8a95f-2cc8-4989-bf6c-ee6361e08ca0?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/b9d8a95f-2cc8-4989-bf6c-ee6361e08ca0?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/b9d8a95f-2cc8-4989-bf6c-ee6361e08ca0?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/b9d8a95f-2cc8-4989-bf6c-ee6361e08ca0?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/b9d8a95f-2cc8-4989-bf6c-ee6361e08ca0?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/4731)
Author
Owner

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

@himself65 linked pull request isn't related !!

@issam-seghir commented on GitHub (Sep 17, 2025): @himself65 linked pull request isn't related !!
Author
Owner

@himself65 commented on GitHub (Sep 17, 2025):

@himself65 linked pull request isn't related !!

Oh sorry, I copy the wrong issue. too many things in the same time

@himself65 commented on GitHub (Sep 17, 2025): > [@himself65](https://github.com/himself65) linked pull request isn't related !! Oh sorry, I copy the wrong issue. too many things in the same time
Author
Owner

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

@dosubot when using autoFill option and focus on email field passkey login is working (email field is filled with passkey name i think not an email)

@issam-seghir commented on GitHub (Sep 17, 2025): @dosubot when using autoFill option and focus on email field passkey login is working (email field is filled with passkey name i think not an email)
Author
Owner

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

This is expected behavior: when you use the autoFill option, Better Auth delegates autofill to the browser and password manager. The client does not control what gets filled—so the email field may be populated with the passkey's display name, username, or any credential label stored by your browser or OS, not necessarily an actual email address. The backend accepts whatever is provided and does not require a strict email format. If you want stricter control over what goes into the email field, you'll need to handle validation or transformation in your own UI before submitting to Better Auth.
See the client implementation

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 17, 2025): <!-- Answer --> This is expected behavior: when you use the autoFill option, Better Auth delegates autofill to the browser and password manager. The client does not control what gets filled—so the email field may be populated with the passkey's display name, username, or any credential label stored by your browser or OS, not necessarily an actual email address. The backend accepts whatever is provided and does not require a strict email format. If you want stricter control over what goes into the email field, you'll need to handle validation or transformation in your own UI before submitting to Better Auth. [See the client implementation](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/passkey/client.ts) <!-- 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/123ae06c-8c4a-4e60-889d-f7c48f1e93cc?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/123ae06c-8c4a-4e60-889d-f7c48f1e93cc?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/123ae06c-8c4a-4e60-889d-f7c48f1e93cc?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/123ae06c-8c4a-4e60-889d-f7c48f1e93cc?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/123ae06c-8c4a-4e60-889d-f7c48f1e93cc?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/123ae06c-8c4a-4e60-889d-f7c48f1e93cc?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/123ae06c-8c4a-4e60-889d-f7c48f1e93cc?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/4731)
Author
Owner

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

@dosubot This is a bit confusing. Email isn’t actually required for passkeys. I think "email" here might just be referring to the passkey name or another credential label stored by the browser or OS, as you mentioned. I can sign in using autofill by focusing on the email input, and i don't need to fill my email address . So either the documentation is incorrect, or this field should be optional.

@issam-seghir commented on GitHub (Sep 17, 2025): @dosubot This is a bit confusing. Email isn’t actually required for passkeys. I think "email" here might just be referring to the passkey name or another credential label stored by the browser or OS, as you mentioned. I can sign in using autofill by focusing on the email input, and i don't need to fill my email address . So either the documentation is incorrect, or this field should be optional.
Author
Owner

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

You're right—email is not actually required for passkey sign-in in Better Auth. The backend and client both treat the email field as optional, and you can call signIn.passkey without it. The confusion comes from the documentation and some example code, which incorrectly show email as required. The field is labeled "email" but really just passes whatever value you provide (which could be a passkey label, username, or credential name from the browser/OS autofill) to the backend, which accepts it as optional. The autofill behavior is handled by the browser, not Better Auth, so what gets filled in that field depends on your environment and stored credentials.

The docs should be updated to clarify that email is optional and not enforced by the API—this is just a documentation issue, not a technical limitation or requirement in the code. You can safely use passkey sign-in without providing an email if your flow supports discoverable credentials.
Client implementation | Demo usage

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 17, 2025): <!-- Answer --> You're right—email is not actually required for passkey sign-in in Better Auth. The backend and client both treat the email field as optional, and you can call signIn.passkey without it. The confusion comes from the documentation and some example code, which incorrectly show email as required. The field is labeled "email" but really just passes whatever value you provide (which could be a passkey label, username, or credential name from the browser/OS autofill) to the backend, which accepts it as optional. The autofill behavior is handled by the browser, not Better Auth, so what gets filled in that field depends on your environment and stored credentials. The docs should be updated to clarify that email is optional and not enforced by the API—this is just a documentation issue, not a technical limitation or requirement in the code. You can safely use passkey sign-in without providing an email if your flow supports discoverable credentials. [Client implementation](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/passkey/client.ts) | [Demo usage](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/demo/nextjs/components/one-tap.tsx) <!-- 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/8f2df59c-c1b5-4d91-aef7-84bfe6c43fd7?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8f2df59c-c1b5-4d91-aef7-84bfe6c43fd7?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8f2df59c-c1b5-4d91-aef7-84bfe6c43fd7?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8f2df59c-c1b5-4d91-aef7-84bfe6c43fd7?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8f2df59c-c1b5-4d91-aef7-84bfe6c43fd7?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8f2df59c-c1b5-4d91-aef7-84bfe6c43fd7?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8f2df59c-c1b5-4d91-aef7-84bfe6c43fd7?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/4731)
Author
Owner

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

@dosubot
After extensive debugging, I discovered that the autoFill method works without requiring the email field. Here’s what happens:

Scenario 1: Autofill (page reload + conditional UI)

  1. Page reloads → all passkeys are preloaded.
  2. User focuses on the email field → browser shows a list of available passkeys.
  3. User selects a passkey → authenticates with Windows Hello PIN (in my case).
  4. User logs in successfully and is redirected to the dashboard.

Works fine, no email required.

  // Preload passkeys for conditional UI (only for login)
    React.useEffect(() => {
        if (type === AuthRoutes.LOGIN && mounted) {
            console.log("🔍 [DEBUG] Passkey preload - checking browser support...");

            // Check if WebAuthn is supported
            if (!window.PublicKeyCredential) {
                console.log("❌ [DEBUG] WebAuthn not supported");
                return;
            }

            console.log("✅ [DEBUG] WebAuthn is supported");

            // Check if browser supports conditional UI and preload passkeys
            if (
                !PublicKeyCredential.isConditionalMediationAvailable ||
                !PublicKeyCredential.isConditionalMediationAvailable()
            ) {
                console.log("❌ [DEBUG] Conditional mediation not available");
                return;
            }

            console.log("✅ [DEBUG] Conditional mediation is available, preloading passkeys...");

            // Start autofill passkey with redirect handling
            authClient.signIn.passkey({
                autoFill: true,
                fetchOptions: {
                    onSuccess: (context) => {
                        console.log("✅ [PASSKEY] Autofill sign-in successful", context);
                        // Trigger redirect after successful authentication
                        window.location.href = callbackUrl || DEFAULT_LOGIN_REDIRECT;
                    },
                    onError: (context) => {
                        console.error("❌ [PASSKEY] Autofill sign-in error:", context.error);
                        // Don't show toast for autofill errors as they're often user cancellations
                    }
                }
            }).catch((error) => {
                // Silent catch for autofill errors (user might cancel or no passkey available)
                console.log("🔍 [DEBUG] Autofill passkey silent error:", error);
            });
        }
    }, [type, mounted, callbackUrl]);

Scenario 2: Manual "Login with Passkey" button

  1. autoFill: true → infinite loading (expected, since autofill shouldn't be used with a manual button).
  2. autoFill: false → user gets a passkey prompt, enters PIN, and logs in successfully (again, no email needed).
<button
                    type="button"
                    className={cn(
                        buttonVariants({ variant: "outline" }),
                        "rtl:font-alexandria border dark:border-[oklch(0.922_0_0)] dark:text-black dark:hover:bg-[oklch(0.97_0_0)] dark:hover:text-black",
                    )}
                    onClick={async () => {
                        setIsPasskeyLoading(true);
                        try {
                            try {
                                const result = await authClient.signIn.passkey({
                                    autoFill: false, // Disable autoFill for manual trigger
                                    fetchOptions: {
                                        onSuccess: (context) => {
                                            console.log("✅ [PASSKEY] Sign-in successful" , context);
                                            authClient.$store.notify("$sessionSignal");
                                            window.location.href = callbackUrl || DEFAULT_LOGIN_REDIRECT;
                                        },
                                        onError: (context) => {
                                            console.error("❌ [PASSKEY] Sign-in error:", context.error);
                                        }
                                    }
                                });

                                // Check for errors first
                                if (result?.error) {
                                    console.error("❌ [PASSKEY] Sign-in error:", result.error);

                                    // Type guard for error with code property
                                    const hasCode = 'code' in result.error && typeof result.error.code === 'string';

                                    if (hasCode && result.error?.code === "PASSKEY_NOT_FOUND") {
                                        toast.error(t("Auth.Form.passkeyNotFoundForEmail"));
                                    } else {
                                        toast.error(
                                            `${t("Auth.Form.passkeyError")} - ${result.error.message || t("Auth.Form.tryAgainLater")}`,
                                        );
                                    }
                                }
                            } catch (passkeyError: unknown) {
                                console.error("❌ [PASSKEY] Sign-in error:", passkeyError);

                                // Handle passkey-specific errors with proper typing
                                const error = passkeyError as Error & { name?: string; message?: string };

                                if (
                                    error.message?.includes("NotAllowed") ||
                                    error.name === "NotAllowedError"
                                ) {
                                    toast.error(
                                        `${t("Auth.Form.passkeyNotAllowed")} - ${t("Auth.Form.passkeyTryAgain")}`,
                                    );
                                } else if (
                                    error.message?.includes("NotSupported") ||
                                    error.name === "NotSupportedError"
                                ) {
                                    toast.error(
                                        `${t("Auth.Form.passkeyNotSupported")} - ${t("Auth.Form.passkeyUseOther")}`,
                                    );
                                } else {
                                    toast.error(
                                        `${t("Auth.Form.passkeyError")} - ${error.message || t("Auth.Form.tryAgainLater")}`,
                                    );
                                }
                            }
                        } catch (error: unknown) {
                            console.error("💥 [PASSKEY] Unexpected error:", error);
                            toast.error(`${t("Auth.Form.passkeyError")} - ${t("Auth.Form.tryAgainLater")}`);
                        } finally {
                            setIsPasskeyLoading(false);
                        }
                    }}
                    disabled={isLoading || isGoogleLoading || isPasskeyLoading || !mounted}
                >
                    {isPasskeyLoading ? (
                        <>
                            <Icons.spinner className="me-2 size-4 animate-spin" />
                            {t("Auth.Form.passkeySignIn")}
                        </>
                    ) : (
                        <>
                            <Icons.fingerprint className="me-2 size-4" />
                            {t("Auth.Form.passkeySignIn")}
                        </>
                    )}
                </button>

⚠️ Confusion / Conflict

The docs say the email field is required in signIn.passkey input.

But in practice:

  • I can sign in without providing email.
  • The "Passkey not found" error I was seeing wasn’t due to missing email , it was because I had multiple passkeys stored for my dev tunnel domain and was selecting the wrong one.

📌 Summary

  • Passkey login works both with autofill and with a manual button without needing the email field.
  • The documentation is misleading because it suggests email is required but doesn't explain what "email" means in this context (is it just a label? an identifier? eamil address?).
  • Doc claim that email is required but it's not in practice
@issam-seghir commented on GitHub (Sep 17, 2025): @dosubot After extensive debugging, I discovered that the autoFill method works without requiring the email field. Here’s what happens: ## Scenario 1: Autofill (page reload + conditional UI) 1. Page reloads → all passkeys are preloaded. 2. User focuses on the email field → browser shows a list of available passkeys. 3. User selects a passkey → authenticates with Windows Hello PIN (in my case). 4. User logs in successfully and is redirected to the dashboard. Works fine, no email required. ```ts // Preload passkeys for conditional UI (only for login) React.useEffect(() => { if (type === AuthRoutes.LOGIN && mounted) { console.log("🔍 [DEBUG] Passkey preload - checking browser support..."); // Check if WebAuthn is supported if (!window.PublicKeyCredential) { console.log("❌ [DEBUG] WebAuthn not supported"); return; } console.log("✅ [DEBUG] WebAuthn is supported"); // Check if browser supports conditional UI and preload passkeys if ( !PublicKeyCredential.isConditionalMediationAvailable || !PublicKeyCredential.isConditionalMediationAvailable() ) { console.log("❌ [DEBUG] Conditional mediation not available"); return; } console.log("✅ [DEBUG] Conditional mediation is available, preloading passkeys..."); // Start autofill passkey with redirect handling authClient.signIn.passkey({ autoFill: true, fetchOptions: { onSuccess: (context) => { console.log("✅ [PASSKEY] Autofill sign-in successful", context); // Trigger redirect after successful authentication window.location.href = callbackUrl || DEFAULT_LOGIN_REDIRECT; }, onError: (context) => { console.error("❌ [PASSKEY] Autofill sign-in error:", context.error); // Don't show toast for autofill errors as they're often user cancellations } } }).catch((error) => { // Silent catch for autofill errors (user might cancel or no passkey available) console.log("🔍 [DEBUG] Autofill passkey silent error:", error); }); } }, [type, mounted, callbackUrl]); ``` ## Scenario 2: Manual "Login with Passkey" button 1. autoFill: true → infinite loading (expected, since autofill shouldn't be used with a manual button). 2. autoFill: false → user gets a passkey prompt, enters PIN, and logs in successfully (again, no email needed). ```ts <button type="button" className={cn( buttonVariants({ variant: "outline" }), "rtl:font-alexandria border dark:border-[oklch(0.922_0_0)] dark:text-black dark:hover:bg-[oklch(0.97_0_0)] dark:hover:text-black", )} onClick={async () => { setIsPasskeyLoading(true); try { try { const result = await authClient.signIn.passkey({ autoFill: false, // Disable autoFill for manual trigger fetchOptions: { onSuccess: (context) => { console.log("✅ [PASSKEY] Sign-in successful" , context); authClient.$store.notify("$sessionSignal"); window.location.href = callbackUrl || DEFAULT_LOGIN_REDIRECT; }, onError: (context) => { console.error("❌ [PASSKEY] Sign-in error:", context.error); } } }); // Check for errors first if (result?.error) { console.error("❌ [PASSKEY] Sign-in error:", result.error); // Type guard for error with code property const hasCode = 'code' in result.error && typeof result.error.code === 'string'; if (hasCode && result.error?.code === "PASSKEY_NOT_FOUND") { toast.error(t("Auth.Form.passkeyNotFoundForEmail")); } else { toast.error( `${t("Auth.Form.passkeyError")} - ${result.error.message || t("Auth.Form.tryAgainLater")}`, ); } } } catch (passkeyError: unknown) { console.error("❌ [PASSKEY] Sign-in error:", passkeyError); // Handle passkey-specific errors with proper typing const error = passkeyError as Error & { name?: string; message?: string }; if ( error.message?.includes("NotAllowed") || error.name === "NotAllowedError" ) { toast.error( `${t("Auth.Form.passkeyNotAllowed")} - ${t("Auth.Form.passkeyTryAgain")}`, ); } else if ( error.message?.includes("NotSupported") || error.name === "NotSupportedError" ) { toast.error( `${t("Auth.Form.passkeyNotSupported")} - ${t("Auth.Form.passkeyUseOther")}`, ); } else { toast.error( `${t("Auth.Form.passkeyError")} - ${error.message || t("Auth.Form.tryAgainLater")}`, ); } } } catch (error: unknown) { console.error("💥 [PASSKEY] Unexpected error:", error); toast.error(`${t("Auth.Form.passkeyError")} - ${t("Auth.Form.tryAgainLater")}`); } finally { setIsPasskeyLoading(false); } }} disabled={isLoading || isGoogleLoading || isPasskeyLoading || !mounted} > {isPasskeyLoading ? ( <> <Icons.spinner className="me-2 size-4 animate-spin" /> {t("Auth.Form.passkeySignIn")} </> ) : ( <> <Icons.fingerprint className="me-2 size-4" /> {t("Auth.Form.passkeySignIn")} </> )} </button> ``` ## ⚠️ Confusion / Conflict The docs say the email field is required in signIn.passkey input. But in practice: - I can sign in without providing email. - The "Passkey not found" error I was seeing wasn’t due to missing email , it was because I had multiple passkeys stored for my dev tunnel domain and was selecting the wrong one. ## 📌 Summary - Passkey login works both with autofill and with a manual button without needing the email field. - The documentation is misleading because it suggests email is required but doesn't explain what "email" means in this context (is it just a label? an identifier? eamil address?). - Doc claim that email is required but it's not in practice
Author
Owner

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

You're absolutely right—passkey sign-in works without the email field in both autofill and manual flows. The client and backend treat the email field as optional; it's just a generic identifier, not strictly an email address, and isn't validated or enforced anywhere in the code. The confusion comes from misleading documentation and example code, which incorrectly show email as required, but you can safely omit it in your implementation. The autofill behavior is handled by the browser, so what gets filled in the field depends on your environment and stored credentials—not Better Auth itself.

This is a documentation issue, not a technical limitation. The docs and examples should be updated to clarify that email is optional and to better explain what the field actually represents. For reference, you can see the client implementation where email is optional here, and ongoing discussion about the misleading docs in issue #2911.

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 17, 2025): <!-- Answer --> You're absolutely right—passkey sign-in works without the email field in both autofill and manual flows. The client and backend treat the email field as optional; it's just a generic identifier, not strictly an email address, and isn't validated or enforced anywhere in the code. The confusion comes from misleading documentation and example code, which incorrectly show email as required, but you can safely omit it in your implementation. The autofill behavior is handled by the browser, so what gets filled in the field depends on your environment and stored credentials—not Better Auth itself. This is a documentation issue, not a technical limitation. The docs and examples should be updated to clarify that email is optional and to better explain what the field actually represents. For reference, you can see the client implementation where email is optional [here](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/passkey/client.ts), and ongoing discussion about the misleading docs in [issue #2911](https://github.com/better-auth/better-auth/issues/2911). <!-- 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/9b25c09d-e35c-4c9b-b6a1-8f13c54d4521?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9b25c09d-e35c-4c9b-b6a1-8f13c54d4521?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9b25c09d-e35c-4c9b-b6a1-8f13c54d4521?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9b25c09d-e35c-4c9b-b6a1-8f13c54d4521?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9b25c09d-e35c-4c9b-b6a1-8f13c54d4521?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9b25c09d-e35c-4c9b-b6a1-8f13c54d4521?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9b25c09d-e35c-4c9b-b6a1-8f13c54d4521?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/4731)
Author
Owner

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

Hi, I think that's a bug in the doc and code. We don't read email actually

@himself65 commented on GitHub (Sep 18, 2025): Hi, I think that's a bug in the doc and code. We don't read email actually
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1952