Enable QR code generation for TOTP authenticator apps in an ASP.NET Core Blazor Web App
Note
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 9 version of this article.
This article explains how to configure an ASP.NET Core Blazor Web App with QR code generation for TOTP authenticator apps.
For an introduction to two-factor authentication (2FA) with authenticator apps using a Time-based One-time Password Algorithm (TOTP), see Enable QR code generation for TOTP authenticator apps in ASP.NET Core.
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 Scaffold Identity in ASP.NET Core projects to scaffold Pages\Manage\EnableAuthenticator
into the app.
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).
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 Scaffold Identity in ASP.NET Core projects. For guidance on using the .NET CLI instead of Visual Studio, see ASP.NET Core code generator tool (`aspnet-codegenerator`).
Adding QR codes to the 2FA configuration page
These instructions use Shim Sangmin's qrcode.js: Cross-browser QRCode generator for JavaScript (davidshimjs/qrcodejs
GitHub repository).
Download the qrcode.min.js
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:
<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 ASP.NET Core Blazor JavaScript with static server-side rendering (static SSR) is adopted.
Add the following JavaScript initializer 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
:
const pageScriptInfoBySrc = new Map();
function registerPageScriptElement(src) {
if (!src) {
throw new Error('Must provide a non-empty value for the "src" attribute.');
}
let pageScriptInfo = pageScriptInfoBySrc.get(src);
if (pageScriptInfo) {
pageScriptInfo.referenceCount++;
} else {
pageScriptInfo = { referenceCount: 1, module: null };
pageScriptInfoBySrc.set(src, pageScriptInfo);
initializePageScriptModule(src, pageScriptInfo);
}
}
function unregisterPageScriptElement(src) {
if (!src) {
return;
}
const pageScriptInfo = pageScriptInfoBySrc.get(src);
if (!pageScriptInfo) {
return;
}
pageScriptInfo.referenceCount--;
}
async function initializePageScriptModule(src, pageScriptInfo) {
if (src.startsWith("./")) {
src = new URL(src.substr(2), document.baseURI).toString();
}
const module = await import(src);
if (pageScriptInfo.referenceCount <= 0) {
return;
}
pageScriptInfo.module = module;
module.onLoad?.();
module.onUpdate?.();
}
function onEnhancedLoad() {
for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) {
if (referenceCount <= 0) {
module?.onDispose?.();
pageScriptInfoBySrc.delete(src);
}
}
for (const { module } of pageScriptInfoBySrc.values()) {
module?.onUpdate?.();
}
}
export function afterWebStarted(blazor) {
customElements.define('page-script', class extends HTMLElement {
static observedAttributes = ['src'];
attributeChangedCallback(name, oldValue, newValue) {
if (name !== 'src') {
return;
}
this.src = newValue;
unregisterPageScriptElement(oldValue);
registerPageScriptElement(newValue);
}
disconnectedCallback() {
unregisterPageScriptElement(this.src);
}
});
blazor.addEventListener('enhancedload', onEnhancedLoad);
}
Add the following shared PageScript
component to the server app.
Components/PageScript.razor
:
<page-script src="@Src"></page-script>
@code {
[Parameter]
[EditorRequired]
public string Src { get; set; } = default!;
}
Add the following collocated JS file 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
:
export function onLoad() {
const uri = document.getElementById('qrCodeData').getAttribute('data-url');
new QRCode(document.getElementById('qrCode'), uri);
}
Under the <PageTitle>
component in the EnableAuthenticator
component, add the PageScript
component with the path to the collocated JS file:
<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 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:
- <div class="alert alert-info">
- Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable
- QR code generation</a>.
- </div>
Locate the two <div>
elements where the QR code should appear and where the QR code data is stored in the page.
Make the following changes:
- For the empty
<div>
, give the element anid
ofqrCode
. - For the
<div>
with thedata-url
attribute, give the element anid
ofqrCodeData
.
- <div></div>
- <div data-url="@authenticatorUri"></div>
+ <div id="qrCode"></div>
+ <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.
In the following example, the default value Microsoft.AspNetCore.Identity.UI
is changed to the company name Weyland-Yutani Corporation
(©1986 20th Century Studios Aliens).
In the GenerateQrCodeUri
method:
- UrlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
+ UrlEncoder.Encode("Weyland-Yutani Corporation"),
Run the app and ensure that the QR code is scannable and that the code validates.
Warning
An ASP.NET Core TOTP code should be kept secret because it can be used to authenticate successfully multiple times before it expires.
EnableAuthenticator
component in reference source
The EnableAuthenticator
component can be inspected in reference source:
EnableAuthenticator
component in reference source
Note
Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
Additional resources
ASP.NET Core