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
IdentityPasskeyOptionsconfigured)
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
- Take a standard ABP v10 MVC tiered solution with
Volo.Abp.Account.Pro.Public.Web. - Override
LoginModeland supply a customLogin.cshtmlthat 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> - Enable passkeys in settings, register a passkey, log out.
- 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/passkeyRequestOptionsreturns 200 with valid options. - 1Password's webauthn shim handles the ceremony, sends its own
using-passkey-successtelemetry, 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 toabp.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
- Confirm where
abp.passkeyis supposed to be defined in v10's Pro Account module. - If it's inline in the stock cshtml, please call this out in the "Customize Login Page" docs as a required snippet to preserve.
- If it's a contributor, please investigate why it isn't running when the page view is overridden.
4 Answer(s)
-
0
abp.passkeyis not defined by the open-sourceabpframework/abprepository (so you won’t find it via GitHub search there). In ABP v10, the Pro Account module’s embeddedPages/Account/Login.jsexpects a global helper namespace (abp.passkey.serializeCredential) to exist, but in your customized login page bundle that helper script is not being included, soabp.passkeystaysundefinedand the click handler throws.With ABP 10.4.x specifically, there is no verified public source (docs or OSS code) that shows where
abp.passkeyis injected/registered for MVC/Razor Pages when usingVolo.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 documentabp.passkey:- (link validated below)
Practical fix (what you already did) is to ensure the helper exists before
Login.jsexecutes. 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, defineabp.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.passkeyto 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) -
0
Hi,
abp.passkey.serializeCredentialisn't defined inline or by a bundle contributor — it lives in a separate embedded file,/Pages/Account/passkey-helpers.js, insideVolo.Abp.Account.Pro.Public.Web. The stockLogin.cshtmladds it to the login script bundle right beforeLogin.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 leavesabp.passkeyundefined — there's no separate contributor adding it for the login page, so nothing puts it back. Add the line back beforeLogin.jsin your custom cshtml and the error goes away; you can then drop yourabp.passkeyshim.For your three asks:
- 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. - 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.
- 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:
serializeCredentialinpasskey-helpers.jsbuilds the JSON by hand on purpose instead of callingPublicKeyCredential.toJSON(), because some password managers (1Password being one) don't implementtoJSON()correctly. Since 1Password is in your test set, the stock helper is safer than atoJSON()shim. It also already serializesclientExtensionResults, so once the helper is back in the bundle you can check whether your separatePasskeyCompatibility.jspatch 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) - It's
-
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)
