Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
1 change: 1 addition & 0 deletions aspnetcore/blazor/fundamentals/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Blazor documentation adopts several conventions for showing and discussing compo
* [Component parameter](xref:blazor/components/index#component-parameters) values lead with a [Razor reserved `@` symbol](xref:mvc/views/razor#razor-syntax), but it isn't required. Literals (for example, boolean values), keywords (for example, `this`), and `null` as component parameter values aren't prefixed with `@`, but this is also merely a documentation convention. Your own code can prefix literals with `@` if you wish.
* C# classes use the [`this` keyword](/dotnet/csharp/language-reference/keywords/this) and avoid prefixing fields with an underscore (`_`) that are assigned to in constructors, which differs from the [ASP.NET Core framework engineering guidelines](https://github.com/dotnet/aspnetcore/wiki/Engineering-guidelines).
* In examples that use [primary constructors (C# 12 or later)](/dotnet/csharp/whats-new/tutorials/primary-constructors), primary constructor parameters are typically used directly by class members.
* In article code examples, the code lines are artificially broken across two or more lines to eliminate or reduce horizontal scrolling of the article's code blocks. The code executes regardless of these artificial line breaks, but you're welcome to condense the code in your own apps by removing the artificial line breaks after you paste the code into a project.

Additional information on Razor component syntax is provided in the *Razor syntax* section of <xref:blazor/components/index#razor-syntax>.

Expand Down
27 changes: 18 additions & 9 deletions aspnetcore/blazor/security/qrcodes-for-authenticator-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ uid: blazor/security/qrcodes-for-authenticator-apps

[!INCLUDE[](~/includes/not-latest-version-without-not-supported-content.md)]

This article explains how to configure an ASP.NET Core Blazor Web App with QR code generation for TOTP authenticator apps.
This article explains how to configure an ASP.NET Core Blazor Web App for two-factor authentication (2FA) with QR codes generated by Time-based One-time Password Algorithm (TOTP) authenticator apps.

For an introduction to two-factor authentication (2FA) with authenticator apps using a Time-based One-time Password Algorithm (TOTP), see <xref:security/authentication/identity-enable-qrcodes>.
For an introduction to 2FA with TOTP authenticator apps, see <xref:security/authentication/identity-enable-qrcodes>.

> [!WARNING]
> An ASP.NET Core TOTP code should be kept secret because it can be used to authenticate successfully multiple times before it expires.
> TOTP codes should be kept secret because they can be used to authenticate multiple times before they expire.

## Scaffold the Enable Authenticator component into the app

Expand All @@ -33,14 +33,16 @@ For more information, see <xref:security/authentication/scaffold-identity>. For

## Adding QR codes to the 2FA configuration page

These instructions use [Shim Sangmin](https://hogangnono.com)'s [qrcode.js: Cross-browser QRCode generator for JavaScript](https://davidshimjs.github.io/qrcodejs/) ([`davidshimjs/qrcodejs` GitHub repository](https://github.com/davidshimjs/qrcodejs)).
A QR code generated by the app to set up 2FA with an TOTP authenticator app must be generated by a QR code library.

The guidance in this article uses [`nimiq/qr-creator`](https://github.com/nimiq/qr-creator), but you can use any QR code generation library.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rick-Anderson Can we update the corresponding content for MVC & Razor Pages as well for consistency? https://learn.microsoft.com/aspnet/core/security/authentication/identity-enable-qrcodes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@guardrex Since this is for Blazor, is there a reason we can't use a .NET QR code generator for this? Maybe https://github.com/manuelbl/QrCodeGenerator?

Copy link
Collaborator Author

@guardrex guardrex Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have that swapped in tomorrow morning.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

@guardrex guardrex Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danroth27 @Rick-Anderson ... Done! 👍 I rolled the new QR code lib into both the BWA 2FA/TOTP article and the new WASM article here. It SUPER cleaned up the insanity on calling the JS lib in the BWA article to generate the QR code. I've also updated the WASM sample app, and you can see the revised Manage2fa component here ...

https://github.com/guardrex/BlazorWebAssemblyStandaloneWithIdentity/blob/main/BlazorWasmAuth/Components/Identity/Manage2fa.razor

@danroth27 ... Rick asked me offline about showing the Login and Manage2fa components here versus cross-linking to them in sample code, so they wouldn't appear in the article. Readers would need to follow a link to reach them.

I agree that they're long, but I don't think it matters all that much. Yeah, they have to scroll, but they don't need to click away from the text, which is something that I hate having to do myself when reading an article.

It would rope devs into taking that code when they go for a basic nuts-'n-bolts experience with the WASM+Identity sample app. However, hacking is so crazy bad these days that perhaps we should force the code on them. Then, should we also place the account confirmation and PW recovery code into the sample app? ... but don't require a confirmed account to log in because the dev then must set up an email provider just to get the basic experiences working as a demo.

Would placing all of these features into the sample app lead us to .... brace yourself ... lead us to the Music Store of Identity sample apps!!?? 🙈😆 ... i.e., ... it just adds so much stuff that it scares the poor new dev or new-to-Blazor dev to death. There are pros and cons to moving the code out of the article and into the sample app. It's your call 👨‍⚖️🏛️. I'm 👂 for your decision.


Download the [`qrcode.min.js`](https://davidshimjs.github.io/qrcodejs/) library to the `wwwroot` folder of the solution's server project. The library has no dependencies.
Download the [`qr-creator.min.js`](https://cdn.jsdelivr.net/npm/qr-creator/dist/qr-creator.min.js) library to the solution's server project. The library has no dependencies. If you intend to use the library to generate other QR codes elsewhere in the app for your own purposes, there's also a module version of the library available from the maintainer (see the [`nimiq/qr-creator`](https://github.com/nimiq/qr-creator) GitHub repository for details).

In the `App` component (`Components/App.razor`), place a library script reference after [Blazor's `<script>` tag](xref:blazor/project-structure#location-of-the-blazor-script):

```razor
<script src="qrcode.min.js"></script>
```html
<script src="qr-creator.min.js"></script>
```

The `EnableAuthenticator` component, which is part of the QR code system in the app and displays the QR code to users, adopts static server-side rendering (static SSR) with enhanced navigation. Therefore, normal scripts can't execute when the component loads or updates under enhanced navigation. Extra steps are required to trigger the QR code to load in the UI when the page is loaded. To accomplish loading the QR code, the approach explained in <xref:blazor/js-interop/ssr> is adopted.
Expand Down Expand Up @@ -72,7 +74,14 @@ Add the following [collocated JS file](xref:blazor/js-interop/javascript-locatio
```javascript
export function onLoad() {
const uri = document.getElementById('qrCodeData').getAttribute('data-url');
new QRCode(document.getElementById('qrCode'), uri);
QrCreator.render({
text: uri,
radius: 0,
ecLevel: 'H',
fill: '#000000',
background: null,
size: 190
}, document.querySelector('#qrCode'));
}
```

Expand Down Expand Up @@ -111,7 +120,7 @@ Make the following changes:
+ <div id="qrCodeData" data-url="@authenticatorUri"></div>
```

Change the site name in the `GenerateQrCodeUri` method of the `EnableAuthenticator` component. The default value is `Microsoft.AspNetCore.Identity.UI`. Change the value to a meaningful site name that users can identify easily in their authenticator app. Developers usually set a site name that matches the company's name. Examples: Yahoo, Amazon, Etsy, Microsoft, Zoho. We recommend limiting the site name length to 30 characters or less to allow the site name to display on narrow mobile device screens.
Change the site name in the `GenerateQrCodeUri` method of the `EnableAuthenticator` component. The default value is `Microsoft.AspNetCore.Identity.UI`. Change the value to a meaningful site name that users can identify easily in their authenticator app. Developers usually set a site name that matches the company's name. We recommend limiting the site name length to 30 characters or less to allow the site name to display on narrow mobile device screens.

In the following example, the default value `Microsoft.AspNetCore.Identity.UI` is changed to the company name `Weyland-Yutani Corporation` (&copy;1986 20th Century Studios [*Aliens*](https://www.20thcenturystudios.com/movies/aliens)).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ author: guardrex
description: Learn how to configure an ASP.NET Core Blazor WebAssembly app with ASP.NET Core Identity with email confirmation and password recovery.
ms.author: riande
monikerRange: '>= aspnetcore-8.0'
ms.date: 10/31/2024
ms.date: 11/21/2024
uid: blazor/security/webassembly/standalone-with-identity/account-confirmation-and-password-recovery
---
# Account confirmation and password recovery in ASP.NET Core Blazor WebAssembly with ASP.NET Core Identity

[!INCLUDE[](~/includes/not-latest-version-without-not-supported-content.md)]

This article explains how to configure an ASP.NET Core Blazor WebAssembly app with ASP.NET Core Identity with email confirmation and password recovery.

> [!NOTE]
> This article only applies standalone Blazor WebAssembly apps with ASP.NET Core Identity. To implement email confirmation and password recovery for Blazor Web Apps, see <xref:blazor/security/account-confirmation-and-password-recovery>.

## Namespace
## Namespaces and article code examples

The namespaces used by the examples in this article are:

Expand Down Expand Up @@ -156,15 +158,11 @@ Locate the line that calls <xref:Microsoft.Extensions.DependencyInjection.Identi
In the client project's `Register` component (`Components/Identity/Register.razor`), change the message to users on a successful account registration to instruct them to confirm their account. The following example includes a link to trigger Identity on the server to resend the confirmation email.

```diff
- <div class="alert alert-success">
- You successfully registered. Now you can <a href="login">login</a>.
- </div>
+ <div class="alert alert-success">
+ You successfully registered. You must now confirm your account by clicking
+ the link in the email that was sent to you. After confirming your account,
+ you can <a href="login">login</a> to the app.
+ <a href="resendConfirmationEmail">Resend confirmation email</a>
+ </div>
- You successfully registered and can <a href="login">login</a> to the app.
+ You successfully registered. You must now confirm your account by clicking
+ the link in the email that was sent to you. After confirming your account,
+ you can <a href="login">login</a> to the app.
+ <a href="resendConfirmationEmail">Resend confirmation email</a>
```

## Update seed data code to confirm seeded accounts
Expand Down Expand Up @@ -222,52 +220,33 @@ public Task<FormResult> ResetPasswordAsync(string email, string resetCode,
In the client project, add implementations for the preceding methods in the `CookieAuthenticationStateProvider` class (`Identity/CookieAuthenticationStateProvider.cs`):

```csharp
/// <summary>
/// Begin the password recovery process by issuing a POST request to the
/// '/forgotPassword' endpoint.
/// </summary>
/// <param name="email">The user's email address.</param>
/// <returns>A <see cref="bool"/> indicating success or failure.</returns>
public async Task<bool> ForgotPasswordAsync(string email)
{
try
{
// make the request
var result = await httpClient.PostAsJsonAsync(
"forgotPassword", new
{
email
});

// successful?
if (result.IsSuccessStatusCode)
{
return true;
}
}
catch { }

// unknown error
return false;
}

/// <summary>
/// Reset the user's password by issuing a POST request to the
/// '/resetPassword' endpoint.
/// </summary>
/// <param name="email">The user's email address.</param>
/// <param name="resetCode">The user's reset code.</param>
/// <param name="newPassword">The user's new password.</param>
/// <returns>The result serialized to a <see cref="FormResult"/>.
/// </returns>
public async Task<FormResult> ResetPasswordAsync(string email, string resetCode,
string newPassword)
{
string[] defaultDetail = ["An unknown error prevented resetting the password."];

try
{
// make the request
var result = await httpClient.PostAsJsonAsync(
"resetPassword", new
{
Expand All @@ -276,13 +255,11 @@ public async Task<FormResult> ResetPasswordAsync(string email, string resetCode,
newPassword
});

// successful?
if (result.IsSuccessStatusCode)
{
return new FormResult { Succeeded = true };
}

// body should contain details about why it failed
var details = await result.Content.ReadAsStringAsync();
var problemDetails = JsonDocument.Parse(details);
var errors = new List<string>();
Expand All @@ -303,7 +280,6 @@ public async Task<FormResult> ResetPasswordAsync(string email, string resetCode,
}
}

// return the error list
return new FormResult
{
Succeeded = false,
Expand All @@ -312,7 +288,6 @@ public async Task<FormResult> ResetPasswordAsync(string email, string resetCode,
}
catch { }

// unknown error
return new FormResult
{
Succeeded = false,
Expand All @@ -323,9 +298,6 @@ public async Task<FormResult> ResetPasswordAsync(string email, string resetCode,

In the client project, add the following `ForgotPassword` component.

> [!NOTE]
> Code lines in the following example are broken across two or more lines to eliminate or reduce horizontal scrolling in this article, but you can place the following code as shown into a test app. The code executes regardless of the artificial line breaks.

`Components/Identity/ForgotPassword.razor`:

```razor
Expand All @@ -344,14 +316,15 @@ In the client project, add the following `ForgotPassword` component.
@if (!passwordResetCodeSent)
{
<EditForm Model="Input" FormName="forgot-password"
OnValidSubmit="OnValidSubmitStep1Async" method="post">
OnValidSubmit="OnValidSubmitStep1Async" method="post">
<DataAnnotationsValidator />
<ValidationSummary class="text-danger" role="alert" />

<div class="form-floating mb-3">
<InputText @bind-Value="Input.Email" id="Input.Email"
class="form-control" autocomplete="username"
aria-required="true" placeholder="[email protected]" />
<InputText @bind-Value="Input.Email"
id="Input.Email" class="form-control"
autocomplete="username" aria-required="true"
placeholder="[email protected]" />
<label for="Input.Email" class="form-label">
Email
</label>
Expand Down Expand Up @@ -389,7 +362,7 @@ In the client project, add the following `ForgotPassword` component.
Obtain the code from the email for this form.
</div>
<EditForm Model="Reset" FormName="reset-password"
OnValidSubmit="OnValidSubmitStep2Async" method="post">
OnValidSubmit="OnValidSubmitStep2Async" method="post">
<DataAnnotationsValidator />
<ValidationSummary class="text-danger" role="alert" />

Expand All @@ -415,7 +388,8 @@ In the client project, add the following `ForgotPassword` component.
class="text-danger" />
</div>
<div class="form-floating mb-3">
<InputText type="password" @bind-Value="Reset.ConfirmPassword"
<InputText type="password"
@bind-Value="Reset.ConfirmPassword"
id="Reset.ConfirmPassword" class="form-control"
autocomplete="new-password" aria-required="true"
placeholder="password" />
Expand All @@ -435,8 +409,7 @@ In the client project, add the following `ForgotPassword` component.
</div>

@code {
private bool passwordResetCodeSent;
private bool passwordResetSuccess, errors;
private bool passwordResetCodeSent, passwordResetSuccess, errors;
private string[] errorList = [];

[SupplyParameterFromForm(FormName = "forgot-password")]
Expand Down Expand Up @@ -490,7 +463,7 @@ In the client project, add the following `ForgotPassword` component.
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation " +
"password do not match.")]
"password don't match.")]
public string ConfirmPassword { get; set; } = string.Empty;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,11 @@ At this point, you must provide custom code to parse the <xref:Microsoft.AspNetC
Scenarios covered by the Blazor documentation set:

* [Account confirmation, password management, and recovery codes](xref:blazor/security/webassembly/standalone-with-identity/account-confirmation-and-password-recovery)
* Two-factor authentication (2FA): *Implementation guidance coming soon!* This work is tracked by [Add 2FA/TOTP coverage to the Standalone+Identity article+sample (`dotnet/AspNetCore.Docs` #33772)](https://github.com/dotnet/AspNetCore.Docs/issues/33772). In the meantime, general information to aid you with a custom implementation is available in <xref:security/authentication/identity/spa#use-the-post-manage2fa-endpoint>.
* [Two-factor authentication (2FA) with a TOTP authenticator app](xref:blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps)

For information on additional Identity scenarios provided by the API, see <xref:security/authentication/identity/spa>:

* Secure selected endpoints
* Two-factor authentication (2FA) and recovery codes
* User info management

## Use secure authentication flows to maintain sensitive data and credentials
Expand Down
Loading