Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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 examples, code lines are split to reduce horizontal scrolling. These breaks don't affect execution but can be removed when pasting into your project.

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

Expand Down
115 changes: 42 additions & 73 deletions aspnetcore/blazor/security/qrcodes-for-authenticator-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,107 +11,62 @@ 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.

## Scaffold the Enable Authenticator component into the app

Follow the guidance in <xref:security/authentication/scaffold-identity#scaffold-identity-into-a-blazor-project> to scaffold `Pages\Manage\EnableAuthenticator` into the app.

<!-- UPDATE 9.0 Update NOTE per followup on the issue -->
The guidance in this article relies upon either creating the app with **Individual Accounts** for the new app's **Authentication type** or [scaffolding Identity into an existing app](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-blazor-project). For guidance on using the .NET CLI instead of Visual Studio for scaffolding Identity into an existing app, see <xref:fundamentals/tools/dotnet-aspnet-codegenerator>.

> [!NOTE]
> Although only the `EnableAuthenticator` component is selected for scaffolding in this example, scaffolding currently adds all of the Identity components to the app. Additionally, exceptions may be thrown during the process of scaffolding into the app. If exceptions occur when database migrations occur, stop the app and restart the app on each exception. For more information, see [Scaffolding exceptions for Blazor Web App (`dotnet/Scaffolding` #2694)](https://github.com/dotnet/Scaffolding/issues/2694).

Be patient while migrations are executed. Depending on the speed of the system, it can take up to a minute or two for database migrations to finish.

For more information, see <xref:security/authentication/scaffold-identity>. For guidance on using the .NET CLI instead of Visual Studio, see <xref:fundamentals/tools/dotnet-aspnet-codegenerator>.
> [!WARNING]
> TOTP codes should be kept secret because they can be used to authenticate multiple times before they expire.

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

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.

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):
A QR code generated by the app to set up 2FA with an TOTP authenticator app must be generated by a QR code library.

```razor
<script src="qrcode.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.

Add the following [JavaScript initializer](xref:blazor/fundamentals/startup#javascript-initializers) to the server project's `wwwroot` folder. The `{NAME}` placeholder must be the name of the app's assembly in order for Blazor to locate and load the file automatically. If the server app's assembly name is `BlazorSample`, the file is named `BlazorSample.lib.module.js`.

`wwwroot/{NAME}.lib.module.js`:
The guidance in this article uses [`manuelbl/QrCodeGenerator`](https://github.com/manuelbl/QrCodeGenerator), but you can use any QR code generation library.

[!INCLUDE[](~/blazor/includes/js-interop/blazor-page-script.md)]
Add a package reference for the [`Net.Codecrete.QrCodeGenerator`](https://www.nuget.org/packages/Net.Codecrete.QrCodeGenerator) NuGet package.

Add the following shared `PageScript` component to the server app.
[!INCLUDE[](~/includes/package-reference.md)]

`Components/PageScript.razor`:
Open the `EnableAuthenticator` component in the `Components/Account/Pages/Manage` folder. At the top of the file under the `@page` directive, add an `@using` directive for the QrCodeGenerator namespace:

```razor
<page-script src="@Src"></page-script>

@code {
[Parameter]
[EditorRequired]
public string Src { get; set; } = default!;
}
```

Add the following [collocated JS file](xref:blazor/js-interop/javascript-location#load-a-script-from-an-external-javascript-file-js-collocated-with-a-component) for the `EnableAuthenticator` component, which is located at `Components/Account/Pages/Manage/EnableAuthenticator.razor`. The `onLoad` function creates the QR code with Sangmin's `qrcode.js` library using the QR code URI produced by the `GenerateQrCodeUri` method in the component's `@code` block.

`Components/Account/Pages/Manage/EnableAuthenticator.razor.js`:

```javascript
export function onLoad() {
const uri = document.getElementById('qrCodeData').getAttribute('data-url');
new QRCode(document.getElementById('qrCode'), uri);
}
@using Net.Codecrete.QrCodeGenerator
```

Under the `<PageTitle>` component in the `EnableAuthenticator` component, add the `PageScript` component with the path to the collocated JS file:

```razor
<PageScript Src="./Components/Account/Pages/Manage/EnableAuthenticator.razor.js" />
```

> [!NOTE]
> An alternative to using the approach with the `PageScript` component is to use an event listener (`blazor.addEventListener("enhancedload", {CALLBACK})`) registered in an [`afterWebStarted` JS initializer](xref:blazor/fundamentals/startup#javascript-initializers) to listen for page updates caused by enhanced navigation. The callback (`{CALLBACK}` placeholder) performs the QR code initialization logic.
>
> Using the callback approach with `enhancedload`, the code executes for every enhanced navigation, even when the QR code `<div>` isn't rendered. Therefore, additional code must be added to check for the presence of the `<div>` before executing the code that adds a QR code.


Delete the `<div>` element that contains the QR code instructions:
Delete the `<div>` element that contains the QR code instructions and the two `<div>` elements where the QR code should appear and where the QR code data is stored in the page:

```diff
- <div class="alert alert-info">
- Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable
Copy link
Member

Choose a reason for hiding this comment

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

Can we get a new go.microsoft.com link that points to this Blazor QR code generation docs? It would be nice to update the Blazor Identity project template and scaffolding to point to this instead. Maybe we could remove <div></div><div data-url="@authenticatorUri"></div> while we're at it.

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

Choose a reason for hiding this comment

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

Unless we need to change the current filename/URL, the GO link destination will be ...

https://learn.microsoft.com/aspnet/core/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps

- QR code generation</a>.
- </div>
- <div></div>
- <div data-url="@authenticatorUri"></div>
```

Locate the two `<div>` elements where the QR code should appear and where the QR code data is stored in the page.
Replace the deleted elements with the following markup:

Make the following changes:
```razor
<div>
<svg xmlns="http://www.w3.org/2000/svg" height="300" width="300" stroke="none"
version="1.1" viewBox="0 0 50 50">
<rect width="300" height="300" fill="#ffffff" />
<path d="@svgGraphicsPath" fill="#000000" />
</svg>
</div>
```

* For the empty `<div>`, give the element an `id` of `qrCode`.
* For the `<div>` with the `data-url` attribute, give the element an `id` of `qrCodeData`.
Just inside the opening `@code` block, change the variable declaration for `authenticatorUri` to `svgGraphicsPath`:

```diff
- <div></div>
- <div data-url="@authenticatorUri"></div>
+ <div id="qrCode"></div>
+ <div id="qrCodeData" data-url="@authenticatorUri"></div>
- private string? authenticatorUri;
+ private string? svgGraphicsPath;
```

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. 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 All @@ -122,6 +77,20 @@ In the `GenerateQrCodeUri` method:
+ UrlEncoder.Encode("Weyland-Yutani Corporation"),
```

At the bottom of the `LoadSharedKeyAndQrCodeUriAsync` method, add the [`var` keyword](/dotnet/csharp/programming-guide/classes-and-structs/implicitly-typed-local-variables) to the line that sets `authenticatorUri`, making it an implicitly-typed local variable:

```diff
- authenticatorUri = GenerateQrCodeUri(email!, unformattedKey!);
+ var authenticatorUri = GenerateQrCodeUri(email!, unformattedKey!);
```

Add the following lines of code at the bottom of the `LoadSharedKeyAndQrCodeUriAsync` method:

```csharp
var qr = QrCode.EncodeText(authenticatorUri, QrCode.Ecc.Medium);
svgGraphicsPath = qr.ToGraphicsPath();
```

Run the app and ensure that the QR code is scannable and that the code validates.

> [!WARNING]
Expand Down
Loading
Loading