Open Closed

abp.passkey is undefined on Login page — passkey login fails with TypeError in Pro Account module #10752


User avatar
0

Environment

  • ABP Framework version: 10.4.1
  • UI Type: MVC (Razor Pages)
  • Database System: EF Core / SQL Server
  • Tiered: Yes
  • Auth Server separated: Yes
  • Theme: LeptonX
  • Browser tested: Edge 135 (also reproduces in Chrome)
  • Identity provider: ASP.NET Core Identity (with IdentityPasskeyOptions configured)

Summary

Clicking the "Passkey login" button on /Account/Login produces an abp.message.error popup with:

Cannot read properties of undefined (reading 'serializeCredential')

The error fires after the WebAuthn ceremony completes successfully (1Password, Windows Hello, and security key all reproduce). The credential is obtained from navigator.credentials.get() and the failure is on the very next line in the embedded Login.js.

Root cause

The embedded /Pages/Account/Login.js resource from Volo.Abp.Account.Pro.Public.Web (resolved through the virtual file system at bundle build) contains the following call:

$("#passkeyLoginButton").click(function (n) {
    // …
    abp.ajax({ url: abp.appPath + "api/account/passkeyRequestOptions", … })
        .done(async function (n) {
            try {
                var u = PublicKeyCredential.parseRequestOptionsFromJSON(n),
                    f = await navigator.credentials.get({ publicKey: u }),
                    e = abp.passkey.serializeCredential(f);   // <-- throws
                // …
            } catch (i) {
                // … abp.message.error(i.message)
            }
        });
});

Verified in DevTools console at the time of the error: typeof abp.passkey === "undefined". Nothing in the generated LoginModel bundle defines that namespace, and a global GitHub search of abpframework/abp for abp.passkey returns no .js hits, so whatever is meant to define it is either inline-only in a Pro cshtml that gets bypassed by view customization, or comes from a contributor that isn't firing in v10.

Reproduction

  1. Take a standard ABP v10 MVC tiered solution with Volo.Abp.Account.Pro.Public.Web.
  2. Override LoginModel and supply a custom Login.cshtml that declares the page's script bundle in the standard way:
    <abp-script-bundle name="@typeof(Volo.Abp.Account.Public.Web.Pages.Account.LoginModel).FullName">
        <abp-script src="/Pages/Account/Login.js" />
    </abp-script-bundle>
    
  3. Enable passkeys in settings, register a passkey, log out.
  4. Click "Passkey login" → ceremony completes → modal shows the TypeError.

Expected behaviour

abp.passkey.serializeCredential should be defined by the module — either by an additional embedded JS file added to the LoginModel bundle via an IBundleContributor, or by an inline script in the stock cshtml that's documented as required when overriding the view. Today neither happens reliably when the cshtml is customized.

Evidence

I captured an Edge DevTools performance trace of the failing flow. Notable from it:

  • The XHR to /api/account/passkeyRequestOptions returns 200 with valid options.
  • 1Password's webauthn shim handles the ceremony, sends its own using-passkey-success telemetry, and the credential is returned to the page.
  • CPU profile in the main JS context goes idle ~500μs after credential delivery and the SweetAlert2 modal appears via LeptonX shortly after — consistent with the catch block firing and calling abp.message.error(i.message).
  • The bundle URL serving Login.js is __bundles/Volo.Abp.Account.Public.Web.Pages.Account.LoginModel.<hash>.js; full text contains exactly one reference to abp.passkey (the failing call) and zero definitions of it.

Happy to attach the .json.gz trace if useful.

Workaround (in place)

We have a separate /Pages/Account/PasskeyCompatibility.js already loading on this page (originally to add a missing clientExtensionResults to the posted JSON). Shimming abp.passkey at the top of that file restores login:

(function () {
    if (window.abp && !abp.passkey) {
        abp.passkey = {
            serializeCredential: function (cred) {
                if (cred && typeof cred.toJSON === "function") {
                    return JSON.stringify(cred.toJSON());
                }
                // base64url fallback for older browsers omitted for brevity
            }
        };
    }
})();

PublicKeyCredential.toJSON() is W3C-standard and produces the AuthenticationResponseJSON shape PasskeyHandler already expects, so the server side requires no change.

Asks

  1. Confirm where abp.passkey is supposed to be defined in v10's Pro Account module.
  2. If it's inline in the stock cshtml, please call this out in the "Customize Login Page" docs as a required snippet to preserve.
  3. If it's a contributor, please investigate why it isn't running when the page view is overridden.
Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

4 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • abp.passkey is not defined by the open-source abpframework/abp repository (so you won’t find it via GitHub search there). In ABP v10, the Pro Account module’s embedded Pages/Account/Login.js expects a global helper namespace (abp.passkey.serializeCredential) to exist, but in your customized login page bundle that helper script is not being included, so abp.passkey stays undefined and the click handler throws.

    • With ABP 10.4.x specifically, there is no verified public source (docs or OSS code) that shows where abp.passkey is injected/registered for MVC/Razor Pages when using Volo.Abp.Account.Pro.Public.Web. The only verified related official doc page about passkeys does not mention any required extra JS snippet when overriding the login view, nor does it document abp.passkey:

      • (link validated below)
    • Practical fix (what you already did) is to ensure the helper exists before Login.js executes. The most robust approach is to add the missing helper to the same script bundle used by the login page (so ordering is guaranteed), e.g. load your shim before /Pages/Account/Login.js:

    <abp-script-bundle name="@typeof(Volo.Abp.Account.Public.Web.Pages.Account.LoginModel).FullName">
          <abp-script src="/Pages/Account/PasskeyCompatibility.js" />
          <abp-script src="/Pages/Account/Login.js" />
      </abp-script-bundle>
    

    And in PasskeyCompatibility.js, define abp.passkey.serializeCredential (your shim approach is fine). This avoids relying on any inline script that might exist in the stock Pro page.

    • Regarding your “Asks” (1–3): I can’t confirm the exact intended source (inline cshtml vs bundle contributor vs separate embedded js) with the currently verifiable public sources/tools available here. So I can’t definitively state where ABP intended abp.passkey to be defined in 10.4.1 Pro.

    Sources:

    • https://abp.io/docs/10.4/modules/account/passkey (validated)

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    abp.passkey.serializeCredential isn't defined inline or by a bundle contributor — it lives in a separate embedded file, /Pages/Account/passkey-helpers.js, inside Volo.Abp.Account.Pro.Public.Web. The stock Login.cshtml adds it to the login script bundle right before Login.js:

    <abp-script-bundle name="@typeof(Volo.Abp.Account.Public.Web.Pages.Account.LoginModel).FullName">
        <abp-script src="/Pages/Account/passkey-helpers.js" />
        <abp-script src="/Pages/Account/Login.js" />
    </abp-script-bundle>
    

    When you override Login.cshtml, your <abp-script-bundle> replaces the whole bundle declaration for that page, so dropping the first line is what leaves abp.passkey undefined — there's no separate contributor adding it for the login page, so nothing puts it back. Add the line back before Login.js in your custom cshtml and the error goes away; you can then drop your abp.passkey shim.

    For your three asks:

    1. It's /Pages/Account/passkey-helpers.js, pulled into the login bundle through that <abp-script> line in the stock page — not a contributor, not an inline script.
    2. Fair point — we'll add a note to the login/passkey customization docs that this line has to be preserved when you replace the page.
    3. Nothing is failing to fire; the helper only reaches the login bundle via the cshtml, so overriding the view without that line simply removes it.

    One note on your shim: serializeCredential in passkey-helpers.js builds the JSON by hand on purpose instead of calling PublicKeyCredential.toJSON(), because some password managers (1Password being one) don't implement toJSON() correctly. Since 1Password is in your test set, the stock helper is safer than a toJSON() shim. It also already serializes clientExtensionResults, so once the helper is back in the bundle you can check whether your separate PasskeyCompatibility.js patch is still doing anything.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0

    Perfect, thank you!

    I've done what you suggested and it works as expected.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Great 👍

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 28, 2026, 11:44
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.