ASP.NET Core Blazor Web アプリで TOTP 認証アプリ用の QR コードを生成できるようにする
この記事では、ASP.NET Core Blazor Web アプリに TOTP 認証アプリ用の QR コード生成を構成する方法について説明します。
時間ベースのワンタイム パスワード アルゴリズム (TOTP) を使用した認証アプリでの 2 要素認証 (2FA) の概要については、「ASP.NET Core で TOTP 認証アプリ用の QR コードを生成できるようにする」を参照してください。
Enable Authenticator コンポーネントをアプリにスキャフォールディングする
「ASP.NET Core プロジェクトでの Identity のスキャフォールディング」のガイダンスに従って、アプリに Pages\Manage\EnableAuthenticator
をスキャフォールディングします。
Note
この例では、スキャフォールディングの対象として EnableAuthenticator
コンポーネントのみが選択されていますが、スキャフォールディングでは現在、Identity のすべてのコンポーネントがアプリに追加されます。 さらに、アプリへのスキャフォールディング プロセス中に例外がスローされる場合があります。 データベースの移行時に例外が発生した場合は、例外が発生するたびにアプリを停止し、アプリを再起動します。 詳細については、「Blazor Web アプリのスキャフォールディングの例外 (dotnet/Scaffolding
#2694)」を参照してください。
移行が実行されている間は、しばらくお待ちください。 システムの速度によっては、データベースの移行が完了するまでに最大 1 - 2 分かかることがあります。
詳細については、「ASP.NET Core プロジェクトの Identity のスキャフォールディング」を参照してください。 Visual Studio ではなく .NET CLI の使用に関するガイダンスについては、ASP.NET Core コード ジェネレーター ツール ('aspnet-codegenerator') に関する記事をご覧ください。
2FA 構成ページへの QR コードの追加
次の手順では、Shim Sangmin の qrcode.js: JavaScript 用のクロスブラウザー QRCode ジェネレーター (davidshimjs/qrcodejs
GitHub リポジトリ) を使用します。
ソリューションのサーバー プロジェクトの wwwroot
フォルダーに qrcode.min.js
ライブラリをダウンロードします。 ライブラリに依存関係はありません。
App
コンポーネント (Components/App.razor
) で、Blazor の <script>
タグの後にライブラリ スクリプト参照を配置します。
<script src="qrcode.min.js"></script>
アプリの QR コード システムの一部であり、QR コードをユーザーに表示する EnableAuthenticator
コンポーネントでは、拡張ナビゲーションを使用した静的サーバー側レンダリング (静的 SSR) が採用されています。 そのため、コンポーネントが拡張ナビゲーションで読み込まれたり更新されたりすると、通常のスクリプトは実行できません。 ページの読み込み時に QR コードの UI への読み込みをトリガーするには、追加の手順が必要です。 QR コードの読み込みを実現するために、「ASP.NET Core Blazor JavaScript と静的サーバー側レンダリング (静的 SSR)」で説明されているアプローチが採用されています。
次の JavaScript 初期化子をサーバー プロジェクトの wwwroot
フォルダーに追加します。 Blazor でファイルを自動的に検索して読み込むには、{NAME}
プレースホルダーがアプリのアセンブリの名前である必要があります。 サーバー アプリのアセンブリ名が BlazorSample
である場合、ファイルには 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);
}
次の共有 PageScript
コンポーネントをサーバー アプリに追加します。
Components/PageScript.razor
:
<page-script src="@Src"></page-script>
@code {
[Parameter]
[EditorRequired]
public string Src { get; set; } = default!;
}
Components/Account/Pages/Manage/EnableAuthenticator.razor
にある EnableAuthenticator
コンポーネント用に、次の併置された JS ファイルを追加します。 onLoad
関数は、Sangmin の qrcode.js
ライブラリで、コンポーネントの @code
ブロック内の GenerateQrCodeUri
メソッドによって生成された QR コード URI を使用して、QR コードを作成します。
Components/Account/Pages/Manage/EnableAuthenticator.razor.js
:
export function onLoad() {
const uri = document.getElementById('qrCodeData').getAttribute('data-url');
new QRCode(document.getElementById('qrCode'), uri);
}
EnableAuthenticator
コンポーネント内の <PageTitle>
コンポーネントの下に、併置された JS ファイルへのパスを含む PageScript
コンポーネントを追加します。
<PageScript Src="./Components/Account/Pages/Manage/EnableAuthenticator.razor.js" />
Note
PageScript
コンポーネントでのアプローチの代わりに使用する方法として、afterWebStarted
JS 初期化子に登録されているイベント リスナー (blazor.addEventListener("enhancedload", {CALLBACK})
) を使用して、拡張ナビゲーションによって起きるページの更新をリッスンします。 コールバック ({CALLBACK}
プレースホルダー) は、QR コード初期化ロジックを実行します。
enhancedload
でのコールバック アプローチを使用すると、QR コード <div>
がレンダリングされない場合でも、すべての拡張ナビゲーションに対してコードが実行されます。 したがって、QR コードを追加するコードを実行する前に、<div>
の存在を確認する追加のコードを加える必要があります。
QR コード命令を含む <div>
要素を削除します。
- <div class="alert alert-info">
- Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable
- QR code generation</a>.
- </div>
ページ内で QR コードを表示する場所と QR コード データが保存される場所の 2 つの <div>
要素を見つけます。
次の変更を行います。
- 空の
<div>
に対して、要素にqrCode
のid
を指定します。 data-url
属性を持つ<div>
に対して、要素にqrCodeData
のid
を指定します。
- <div></div>
- <div data-url="@authenticatorUri"></div>
+ <div id="qrCode"></div>
+ <div id="qrCodeData" data-url="@authenticatorUri"></div>
EnableAuthenticator
コンポーネントの GenerateQrCodeUri
メソッド内のサイト名を変更します。 既定値は Microsoft.AspNetCore.Identity.UI
です。 ユーザーが認証アプリで他のアプリの他の QR コードと並べて簡単に識別できるように、値をわかりやすいサイト名に変更します。 値の URL はエンコードされたままにします。 開発者は通常、会社の名前と一致するサイト名を設定します。 例: Yahoo、Amazon、Etsy、Microsoft、Zoho。
次の例では、{SITE NAME}
プレースホルダーがサイト (会社) 名の箇所です。
private string GenerateQrCodeUri(string email, string unformattedKey)
{
return string.Format(
CultureInfo.InvariantCulture,
AuthenticatorUriFormat,
- UrlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
+ UrlEncoder.Encode("{SITE NAME}"),
UrlEncoder.Encode(email),
unformattedKey);
}
アプリを実行し、QR コードがスキャン可能であること、コードが検証されることを確認します。
参照元の EnableAuthenticator
コンポーネント
参照元で EnableAuthenticator
コンポーネントを検査できます。
参照元の EnableAuthenticator
コンポーネント
Note
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
その他のリソース
ASP.NET Core