Skip to content

Commit 337ffaf

Browse files
authored
Merge pull request #36088 from dotnet/main
2 parents dfd3d07 + f909fce commit 337ffaf

File tree

3 files changed

+117
-13
lines changed

3 files changed

+117
-13
lines changed

aspnetcore/blazor/components/sections.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn how to control the content in a Razor component from a child
55
monikerRange: '>= aspnetcore-8.0'
66
ms.author: wpickett
77
ms.custom: mvc
8-
ms.date: 11/12/2024
8+
ms.date: 09/10/2025
99
uid: blazor/components/sections
1010
---
1111
# ASP.NET Core Blazor sections
@@ -85,6 +85,10 @@ When the `Counter` component is accessed, the `MainLayout` component renders the
8585
> [!NOTE]
8686
> <xref:Microsoft.AspNetCore.Components.Sections.SectionOutlet> and <xref:Microsoft.AspNetCore.Components.Sections.SectionContent> components can only set either <xref:Microsoft.AspNetCore.Components.Sections.SectionOutlet.SectionId%2A> or <xref:Microsoft.AspNetCore.Components.Sections.SectionOutlet.SectionName%2A>, not both.
8787
88+
## `RenderFragment` caching rules and section rendering behavior
89+
90+
When a <xref:Microsoft.AspNetCore.Components.Sections.SectionContent>'s <xref:Microsoft.AspNetCore.Components.RenderFragment> content changes, which is a different instance than the component where it's rendered, Blazor completely destroys and recreates the section instead of attempting to update the section's content. Unlike normal rendering, the section's content could come from different instances, and it doesn't make sense to attempt processing content from two separate components, which might lead to unexpected results. For a detailed explanation on this behavior, see [Inconsistent component initialization with Blazor SectionOutlet/SectionContent and CascadingValue (`dotnet/aspnetcore` #58316)](https://github.com/dotnet/aspnetcore/issues/58316).
91+
8892
## Section interaction with other Blazor features
8993

9094
A section interacts with other Blazor features in the following ways:

aspnetcore/security/authentication/passkeys/blazor.md

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ title: Implement passkeys in ASP.NET Core Blazor Web Apps
33
author: guardrex
44
description: Learn how to implement passkeys authentication in ASP.NET Core Blazor Web Apps.
55
ms.author: wpickett
6+
monikerRange: '>= aspnetcore-10.0'
67
ms.custom: mvc
7-
ms.date: 09/08/2025
8+
ms.date: 09/10/2025
89
uid: security/authentication/passkeys/blazor
910
zone_pivot_groups: implementation
1011
---
@@ -237,23 +238,26 @@ Add the following `RenamePasskey` component for renaming passkeys and update the
237238

238239
Add a link to the passkey management page in the app's `ManageNavMenu` component.
239240

240-
In `Components/Account/Shared/ManageNavMenu.razor`:
241+
In `Components/Account/Shared/ManageNavMenu.razor`, add the following [`NavLink` component](xref:blazor/fundamentals/routing#navlink-component) for the `Passkeys` component:
241242

242-
```diff
243-
+ <li class="nav-item">
244-
+ <NavLink class="nav-link" href="Account/Manage/Passkeys">Passkeys</NavLink>
245-
+ </li>
243+
```razor
244+
<li class="nav-item">
245+
<NavLink class="nav-link" href="Account/Manage/Passkeys">Passkeys</NavLink>
246+
</li>
246247
```
247248

248249
## Include the JavaScript file
249250

250-
In the `App` component, add a reference to the `PasskeySubmit` JavaScript file after the Blazor script.
251+
In the `App` component (`Components/App.razor`), locate the [Blazor script](xref:blazor/project-structure#location-of-the-blazor-script) tag:
251252

252-
In `Components/App.razor`:
253-
254-
```diff
253+
```razor
255254
<script src="_framework/blazor.web.js"></script>
256-
+ <script src="Components/Account/Shared/PasskeySubmit.razor.js" type="module"></script>
255+
```
256+
257+
Immediately after the Blazor script tag, add a reference to the `PasskeySubmit` JavaScript module:
258+
259+
```razor
260+
<script src="Components/Account/Shared/PasskeySubmit.razor.js" type="module"></script>
257261
```
258262

259263
:::zone-end
@@ -279,6 +283,14 @@ After a passkey is registered:
279283
1. Navigate to `Account/Manage/Passkeys` to add, rename, or delete passkeys.
280284
1. If the passkey supports passkey autofill (conditional UI) for login, test the passkey autofill feature by selecting the email input field when you have saved passkeys.
281285

286+
## Mitigate `PublicKeyCredential.toJSON` error (`TypeError: Illegal invocation`)
287+
288+
Some password managers don't implement the [`PublicKeyCredential.toJSON` method](https://developer.mozilla.org/docs/Web/API/PublicKeyCredential/toJSON) correctly, which is required for [`JSON.stringify`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) to work when serializing passkey credentials. When registering or authenticating a user with an app based on the Blazor Web App project template, the following error is thrown when attempting to add a passkey:
289+
290+
> :::no-loc text="Error: Could not add a passkey: Illegal invocation":::
291+
292+
For guidance on mitigating this error, see <xref:security/authentication/passkeys/index#mitigate-publickeycredentialtojson-error-typeerror-illegal-invocation>.
293+
282294
## Additional resources
283295

284296
* [Web Authentication API (MDN documentation)](https://developer.mozilla.org/docs/Web/API/Web_Authentication_API)

aspnetcore/security/authentication/passkeys/index.md

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ author: guardrex
44
description: Discover how to enable Web Authentication API (WebAuthn) passkeys in ASP.NET Core apps.
55
ms.author: wpickett
66
monikerRange: '>= aspnetcore-10.0'
7-
ms.date: 09/08/2025
7+
ms.custom: mvc
8+
ms.date: 09/10/2025
89
uid: security/authentication/passkeys/index
910
---
1011
# Enable Web Authentication API (WebAuthn) passkeys
@@ -573,6 +574,93 @@ For scenarios requiring more control, you can use `PerformPasskeyAssertionAsync`
573574

574575
Upon successful authentication, ASP.NET Core Identity establishes an authenticated session for the user. The `PasskeySignInAsync` method handles this automatically, creating the necessary authentication cookies and claims. The app then redirects the user to protected resources or display personalized content.
575576

577+
## Mitigate `PublicKeyCredential.toJSON` error (`TypeError: Illegal invocation`)
578+
579+
The [`PublicKeyCredential.toJSON` method](https://developer.mozilla.org/docs/Web/API/PublicKeyCredential/toJSON) returns a JSON representation of a [`PublicKeyCredential`](https://developer.mozilla.org/docs/Web/API/PublicKeyCredential). The method is invoked by the password manager when the app attempts to serialize a `PublicKeyCredential` by calling [`JSON.stringify`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) while registering or authenticating a user.
580+
581+
Some password managers don't implement the `PublicKeyCredential.toJSON` method correctly, which is required for `JSON.stringify` to work when serializing passkey credentials. When registering or authenticating a user with an app based on the Blazor Web App project template, the following error is thrown by some password managers when attempting to add a passkey:
582+
583+
> :::no-loc text="Error: Could not add a passkey: Illegal invocation":::
584+
585+
Until your selected password manager is updated to implement the `PublicKeyCredential.toJSON` method correctly, make the following changes to the app. The following code manually JSON serializes the `PublicKeyCredential`.
586+
587+
In the `Components/Account/Shared/PasskeySubmit.razor.js` file, locate the `passkey-submit` custom element definition code block:
588+
589+
```javascript
590+
customElements.define('passkey-submit', class extends HTMLElement {
591+
...
592+
});
593+
```
594+
595+
Add the following `convertToBase64` function to the code block:
596+
597+
```javascript
598+
convertToBase64(o) {
599+
if (!o) {
600+
return undefined;
601+
}
602+
603+
// Normalize Array to Uint8Array
604+
if (Array.isArray(o)) {
605+
o = Uint8Array.from(o);
606+
}
607+
608+
// Normalize ArrayBuffer to Uint8Array
609+
if (o instanceof ArrayBuffer) {
610+
o = new Uint8Array(o);
611+
}
612+
613+
// Convert Uint8Array to base64
614+
if (o instanceof Uint8Array) {
615+
let str = '';
616+
for (let i = 0; i < o.byteLength; i++) {
617+
str += String.fromCharCode(o[i]);
618+
}
619+
o = window.btoa(str);
620+
}
621+
622+
if (typeof o !== 'string') {
623+
throw new Error("Could not convert to base64 string");
624+
}
625+
626+
// Convert base64 to base64url
627+
o = o.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, "");
628+
629+
return o;
630+
}
631+
```
632+
633+
In the `obtainAndSubmitCredential` function of the code block, locate the line that calls `JSON.stringify` with the user's credential and remove the line:
634+
635+
```diff
636+
- const credentialJson = JSON.stringify(credential);
637+
```
638+
639+
Replace the preceding line with the following code:
640+
641+
```javascript
642+
const credentialJson = JSON.stringify({
643+
authenticatorAttachment: credential.authenticatorAttachment,
644+
clientExtensionResults: credential.getClientExtensionResults(),
645+
id: credential.id,
646+
rawId: this.convertToBase64(credential.rawId),
647+
response: {
648+
attestationObject: this.convertToBase64(credential.response.attestationObject),
649+
authenticatorData: this.convertToBase64(credential.response.authenticatorData ??
650+
credential.response.getAuthenticatorData?.() ?? undefined),
651+
clientDataJSON: this.convertToBase64(credential.response.clientDataJSON),
652+
publicKey: this.convertToBase64(credential.response.getPublicKey?.() ?? undefined),
653+
publicKeyAlgorithm: credential.response.getPublicKeyAlgorithm?.() ?? undefined,
654+
transports: credential.response.getTransports?.() ?? undefined,
655+
signature: this.convertToBase64(credential.response.signature),
656+
userHandle: this.convertToBase64(credential.response.userHandle),
657+
},
658+
type: credential.type,
659+
});
660+
```
661+
662+
The preceding workaround is only required until the password manager is updated to implement the `PublicKeyCredential.toJSON` method correctly. We recommend tracking your password manager's release notes and reverting the preceding changes after the password manager is updated.
663+
576664
## Additional resources
577665
578666
* [Web Authentication API (MDN documentation)](https://developer.mozilla.org/docs/Web/API/Web_Authentication_API)

0 commit comments

Comments
 (0)