Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion aspnetcore/blazor/components/sections.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to control the content in a Razor component from a child
monikerRange: '>= aspnetcore-8.0'
ms.author: wpickett
ms.custom: mvc
ms.date: 11/12/2024
ms.date: 09/10/2025
uid: blazor/components/sections
---
# ASP.NET Core Blazor sections
Expand Down Expand Up @@ -85,6 +85,10 @@ When the `Counter` component is accessed, the `MainLayout` component renders the
> [!NOTE]
> <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.

## `RenderFragment` caching rules and section rendering behavior

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).

## Section interaction with other Blazor features

A section interacts with other Blazor features in the following ways:
Expand Down
34 changes: 23 additions & 11 deletions aspnetcore/security/authentication/passkeys/blazor.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ title: Implement passkeys in ASP.NET Core Blazor Web Apps
author: guardrex
description: Learn how to implement passkeys authentication in ASP.NET Core Blazor Web Apps.
ms.author: wpickett
monikerRange: '>= aspnetcore-10.0'
ms.custom: mvc
ms.date: 09/08/2025
ms.date: 09/10/2025
uid: security/authentication/passkeys/blazor
zone_pivot_groups: implementation
---
Expand Down Expand Up @@ -237,23 +238,26 @@ Add the following `RenamePasskey` component for renaming passkeys and update the

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

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

```diff
+ <li class="nav-item">
+ <NavLink class="nav-link" href="Account/Manage/Passkeys">Passkeys</NavLink>
+ </li>
```razor
<li class="nav-item">
<NavLink class="nav-link" href="Account/Manage/Passkeys">Passkeys</NavLink>
</li>
```

## Include the JavaScript file

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

In `Components/App.razor`:

```diff
```razor
<script src="_framework/blazor.web.js"></script>
+ <script src="Components/Account/Shared/PasskeySubmit.razor.js" type="module"></script>
```

Immediately after the Blazor script tag, add a reference to the `PasskeySubmit` JavaScript module:

```razor
<script src="Components/Account/Shared/PasskeySubmit.razor.js" type="module"></script>
```

:::zone-end
Expand All @@ -279,6 +283,14 @@ After a passkey is registered:
1. Navigate to `Account/Manage/Passkeys` to add, rename, or delete passkeys.
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.

## Mitigate `PublicKeyCredential.toJSON` error (`TypeError: Illegal invocation`)

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:

> :::no-loc text="Error: Could not add a passkey: Illegal invocation":::

For guidance on mitigating this error, see <xref:security/authentication/passkeys/index#mitigate-publickeycredentialtojson-error-typeerror-illegal-invocation>.

## Additional resources

* [Web Authentication API (MDN documentation)](https://developer.mozilla.org/docs/Web/API/Web_Authentication_API)
Expand Down
90 changes: 89 additions & 1 deletion aspnetcore/security/authentication/passkeys/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ author: guardrex
description: Discover how to enable Web Authentication API (WebAuthn) passkeys in ASP.NET Core apps.
ms.author: wpickett
monikerRange: '>= aspnetcore-10.0'
ms.date: 09/08/2025
ms.custom: mvc
ms.date: 09/10/2025
uid: security/authentication/passkeys/index
---
# Enable Web Authentication API (WebAuthn) passkeys
Expand Down Expand Up @@ -573,6 +574,93 @@ For scenarios requiring more control, you can use `PerformPasskeyAssertionAsync`

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.

## Mitigate `PublicKeyCredential.toJSON` error (`TypeError: Illegal invocation`)

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.

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:

> :::no-loc text="Error: Could not add a passkey: Illegal invocation":::

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`.

In the `Components/Account/Shared/PasskeySubmit.razor.js` file, locate the `passkey-submit` custom element definition code block:

```javascript
customElements.define('passkey-submit', class extends HTMLElement {
...
});
```

Add the following `convertToBase64` function to the code block:

```javascript
convertToBase64(o) {
if (!o) {
return undefined;
}

// Normalize Array to Uint8Array
if (Array.isArray(o)) {
o = Uint8Array.from(o);
}

// Normalize ArrayBuffer to Uint8Array
if (o instanceof ArrayBuffer) {
o = new Uint8Array(o);
}

// Convert Uint8Array to base64
if (o instanceof Uint8Array) {
let str = '';
for (let i = 0; i < o.byteLength; i++) {
str += String.fromCharCode(o[i]);
}
o = window.btoa(str);
}

if (typeof o !== 'string') {
throw new Error("Could not convert to base64 string");
}

// Convert base64 to base64url
o = o.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, "");

return o;
}
```

In the `obtainAndSubmitCredential` function of the code block, locate the line that calls `JSON.stringify` with the user's credential and remove the line:

```diff
- const credentialJson = JSON.stringify(credential);
```

Replace the preceding line with the following code:

```javascript
const credentialJson = JSON.stringify({
authenticatorAttachment: credential.authenticatorAttachment,
clientExtensionResults: credential.getClientExtensionResults(),
id: credential.id,
rawId: this.convertToBase64(credential.rawId),
response: {
attestationObject: this.convertToBase64(credential.response.attestationObject),
authenticatorData: this.convertToBase64(credential.response.authenticatorData ??
credential.response.getAuthenticatorData?.() ?? undefined),
clientDataJSON: this.convertToBase64(credential.response.clientDataJSON),
publicKey: this.convertToBase64(credential.response.getPublicKey?.() ?? undefined),
publicKeyAlgorithm: credential.response.getPublicKeyAlgorithm?.() ?? undefined,
transports: credential.response.getTransports?.() ?? undefined,
signature: this.convertToBase64(credential.response.signature),
userHandle: this.convertToBase64(credential.response.userHandle),
},
type: credential.type,
});
```

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.

## Additional resources

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