WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

MVC5 アプリに 2 要素認証を実装

by WebSurfer 27. October 2021 12:34

Visual Studio 2019 のテンプレートを使って作成した ASP.NET MVC5 アプリには、SMS と Email を利用した 2 要素認証を使用するためのコードが組み込まれています。今更ながらですが、その機能を検証してみました。その際に備忘録として残しておくのが良さそうと思った点を以下に書いておきます。

SQL Server のユーザー情報

まず最初に、ASP.NET MVC5 が何をベースに 2 要素認証を行っているかを書きます。これを覚えておくと、この先の話が分かりやすいかと思いますので。

上の画像を見てください。これは ASP.NET Identity が使う SQL Server データベースの AspNetUsers テーブルで、この例ではアプリを実行して登録した 2 人のユーザー情報が含まれています。

2 要素認証はユーザーごとに有効 / 無効の設定ができます。有効に設定すると当該ユーザーの TwoFactorEnabled 列が True になります。

有効にしただけでは 2 要素認証は働きません。さらに条件があって、メールアドレスと SMS 用の電話番号がそれぞれ Email 列 と PhoneNumber 列に登録済みで(片方のみでも可)、その有効性が確認されて EmailConfirmed 列と PhoneNumberConfirmed 列が True になっている必要があります。

上の画像の2 番目のユーザー (Id が 7c...) がその条件を満たしています。このユーザーはログイン画面で id とパスワードを入力するだけではログインできず、その後 2 要素認証用トークンの確認画面にリダイレクトされます。別途 SMS または Email でトークンが送信されてきますので、それを確認画面に入力するとログインできます。

上の画像の 1 番目のユーザー (Id が 30...) も Manage/Index 画面で 2 要素認証を有効に(TwoTactorEnabled 列を True に)設定することはできます。しかし、EmailConfirmed 列と PhoneNumberConfirmed 列が False となっている場合は 2 要素認証は働かず、ログイン画面で id とパスワードを入力するだけでログインできてしまいます。

Email による 2 要素認証を可能にするためには AspNetUsers テーブルの Email 列に有効なメールアドレスを登録し、EmailConfirmed 列を True にする必要があります。通常それは、先の記事「Email Confirmation の実装 (MVC5)」で書いた機能で、ユーザー登録の際に自動的に設定されます。

ASP.NET Identity 2.0 ではデフォルトで id にメールアドレスを使っていますので、ユーザー登録するだけで AspNetUsers テーブルの Email 列にはメールアドレスが登録されますが、EmailConfirmed 列は False のままです。Email Confirmation 機能によりメールを受け取って、その本文にあるリンクをクリックすることにより、EmailConfirmed 列が True に設定されます。

SMS による 2 要素認証を可能にするには、PhoneNumber 列への SMS 送信先の電話番号の登録が必要になります。その操作はユーザー登録後に Manage/Index 画面にて行います。電話番号を登録すると SMS に確認用トークンが送信されてくるので、それを入力して確認することにより PhoneNumberConfirmed 列が Ture に設定されます。(詳細後述)


2 要素認証の実装方法は Microsoft のチュートリアル「SMS や電子メールで 2 要素認証する ASP.NET MVC 5 アプリ」に書かれています。

ただし、チュートリアルの手順では実際に SMS と Email の送信機能を実装しないと検証ができないのが難点です。Email Confirmation 用に SendGrid にユーザー登録して ApiKey を取得し、メールを送信できるようにするのは相当面倒でした。SMS の方は試してないのでどのぐらい面倒かは分かりませんが、チュートリアルにある Twilio のリンクをクリックしただけでその先に進んでユーザー登録しようという気力が失われました。(汗)

実際に SMS と Email でトークンを受け取ることができるのを確認しないと意味がないと言われるかもしれませんが、SMS と Email の送信機能を実装するところで挫折して先に進めないかもしれません。とりあえず基本を把握しておいて、後で実際に運用に使うプロバイダが決まってから Email や SMS の送信機能を実装して検証する方が良さそうだと思いました。

という訳で、Visual Studio 2019 のテンプレートを使って作成したプロジェクトをベースに SMS と Email の送信機能は実装しないでやってみました。

まずプロジェクトの作成ですが、チュートリアルと同様に .NET Framework 版の ASP.NET MVC5 アプリのプロジェクトを、認証に「個別のユーザーアカウント」 を選択して作成します。Core 版と間違えないよう注意してください。(下の画像は Visual Studio 2019 のもので、チュートリアルのものとは異なります)

プロパティの作成

プロジェクト作成の後、チュートリアルには書いてありませんが、ASP.NET Identity が使うユーザー情報のストアとしての SQL Server データベースの生成と、ユーザー登録が必要です。アプリを実行してブラウザ上でユーザー登録を行えば、EF Code First の機能を使ってのデータベースの作成と、データベースへのユーザー情報の登録が自動的に行われます。

ユーザー登録に成功してログイン状態になると、画面の右上に「ようこそ・・・」と表示されます。それをクリックすると以下のような Manage/Index 画面に遷移します。

Manage/Index 画面

上の画像では「2 要素認証: 2 要素認証プロバイダーが構成されていません・・・」と表示されています。それを変更して 2 要素認証の設定ができるようにします。そのためには、チュートリアルの「7.Views\Manage\Index.cshtml Razor ビューを更新します」にあるように Views\Manage\Index.cshtml に手を加えます。(手順 1 ~ 6 は SMS 送信機能の実装ですので今回はスキップ)

Visual Studio 2019 のテンプレートで作成した場合コードは生成済みでコメントアウトされていますので、それを以下のようにすれば OK です。だたし、テンプレートで生成されたコードには &nbsp;&nbsp;]</TEXT> が欠落していて、そのままではエラーになるので、下の画像の赤枠の部分のコードを追加してください。

Index.cshtml Razor ビューを更新

上の Views\Manage\Index.cshtml の更新後、Manage/Index 画面を表示すると以下のようになります。

Razor ビュー更新後の Manage/Index 画面

上の画像の「電話番号」というのは SMS を利用した 2 要素認証を行う場合の SMS 送信先です。登録してない場合は Add と言うリンクが表示されますので、それをクリックすると Manage/AddPhoneMumber に遷移します。

SMS 用の電話番号の登録

そこで SMS 送信先の電話番号を入力して[送信]ボタンをクリックすると、テンプレートで実装された Manage/AddPhoneNumber アクションメソッドのコードがトークンを生成して SmsService クラスの SendAsync メソッドを実行し、SMS を使って登録した電話番号にトークンを送信します。

この記事では SMS 送信用のコードは実装してませんので何も起こりませんが、以下のようにデバッガを使えばトークンが message の Body に含まれているのが確認できます。

SMS でトークン送信

SMS 送信後、確認画面 Manage/VerifyPhoneNumber にリダイレクトされます。

確認画面でトークンの入力

SMS で送られてきたトークンを上の確認画面の[コード]欄に入力して[送信]ボタンをクリックすると Manage/Index にリダイレクトされ、以下のように登録された SMS 用の電話番号が表示されます。

登録された SMS 用の電話番号

同時に、入力した電話番号は SQL Server データベースの AspNetUsers テーブルの当該ユーザーのレコードの PhoneNumber に登録され、PhoneNumberConfirmed が true になります。

この記事のように SMS の送信機能を実装しない場合、上の画像のようにデバッガでトークンを確認してから確認画面に入力するのは面倒ですよね。そこを何とかしたいという場合は、トークンの生成のコードは確認画面用の Manage/VerifyPhoneNumber アクションメソッドにもありますので、便宜的に(あくまで便宜的にです)以下のようにして ViewBag を使って確認画面に表示することができます。

// GET: /Manage/VerifyPhoneNumber
public async Task<ActionResult> VerifyPhoneNumber(string phoneNumber)
{
    var code = await UserManager
                     .GenerateChangePhoneNumberTokenAsync(
                         User.Identity.GetUserId(), phoneNumber);

    // SMS は使えないので ViewBag を使って View に表示されるようにした
    ViewBag.Token = code;

    // 電話番号を確認するために SMS プロバイダー経由で SMS を送信します。
    return phoneNumber == null ? View("Error") : 
        View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}

以上は SMS 送信先の電話番号を登録するだけの設定です。以下に、2 要素認証を行うために、登録した SMS 用の電話番号または Email アドレス宛にトークンを送るための設定を書きます。

・・・と言っても、そのためのコードは Visual Studio 2019 のテンプレートで作成したプロジェクトにほぼ実装済みで、自力でコードを書いて実装しなければならないのは Email, SMS が送信できるようにするための機能だけです。

それらの機能は IdentityConfig.cs の EmailService クラスと SmsService クラスの中の SendAsync メソッドに実装しますが、上に書きましたようにこの記事ではとりあえずそこはスキップします。

2 要素認証プロバイダーは、SMS と Email 用については自動生成された IdentityConfig.cs の ApplicationUserManager クラスの Create メソッドで manager.RegisterTwoFactorProvider メソッドを使って以下のように登録済みです。EmailService クラスと SmsService クラスもインスタンス化されて ApplicationUserManager に設定されています。

manager.RegisterTwoFactorProvider("電話コード", 
    new PhoneNumberTokenProvider<ApplicationUser>
{
    MessageFormat = "あなたのセキュリティ コードは {0} です。"
});

manager.RegisterTwoFactorProvider("電子メール コード", 
    new EmailTokenProvider<ApplicationUser>
{
    Subject = "セキュリティ コード",
    BodyFormat = "あなたのセキュリティ コードは {0} です。"
});

manager.EmailService = new EmailService();
manager.SmsService = new SmsService();

2 要素認証の有効 / 無効はユーザーごとに設定します。Manage/Index 画面で[2 要素認証:]の[有効化]をクリックすると、SQL Server データベースの AspNetUsers テーブルの当該ユーザーの TwoFactorEnabled 列が True に変わります。

この設定により、ログインの際に SignInManager.PasswordSignInAsync メソッドの戻り値が RequiresVerification になって Account/SendCode にリダイレクトされるようになります。

この先に進んで Email による 2 要素認証が動くようにするには、この記事の一番上の画像にあるように当該ユーザーのレコードの EmailConfirmed 列が True になっていなければなりません。先の記事「Email Confirmation の実装 (MVC5)」で書きました機能が実装されていてユーザー登録の際 Email Confirmation が行われていれば True になっているはずです。Email Confirmation 機能が実装されてなければ、Visual Studio の「SQL Server オブジェクトエクスプローラー」で AspNetUsers テーブルを開いて手動で EmailConfirmed 列を True に設定してください。

それで以下の画像のように Account/SendCode にリダイレクトされた時にドロップダウンリストに[電話コード]と[電子メール コード]という選択肢が表示されるようになります。 (EmailConfirmed 列が False のままですとドロップダウンリストには[電話コード]しか表示されません)

Account/SendCode

ドロップダウンリストの[電話コード]が SMS 用で[電子メール コード]が Email 用です。分かり難い日本語ですが、それらの文字列は上のコードのとおり IdentityConfig.cs の ApplicationUserManager クラスで登録したときのもので、任意の文字列に変更可能です。

ドロップダウンリストの[電話コード]を選択して[送信]ボタンをクリックすると SMS で、[電子メール コード]を選択すると Email でトークンが送信されます。その操作は POST 側の Account/SendCode アクションメソッドの SignInManager.SendTwoFactorCodeAsync メソッドで行われます。SMS を使うか Email を使うかの切り替えは、ドロップダウンリストの選択に応じて、メソッド内部で自動的に行われます。

トークンの送信後、下の画像のように Account/VerifyCode にリダイレクトされます。赤枠で囲った部分は検証中に便宜を図るため自分が追加したもので、元のコードには含まれていません。

Account/VerifyCode

Account/VerifyCode 画面の[コード]テキストボックスに SMS または Email で送信されてきたトークンを入力して[送信]ボタンをクリックするとログインできます。


ついでに、本題とは関係ない話ですが、検証の際の便宜を図るために (いちいちデバッガで SendAsync メソッドの引数に含まれるトークンを調べなくても済むようにするため)、上の画像の赤枠で囲ったようにトークンを Account/VerifyCode 画面に表示する方法を書いておきます。トークンは以下のように Account/SendCode アクションメソッドで取得します。

// POST: /Account/SendCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View();
    }

    // トークンを生成して送信します。
    if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider))
    {
        return View("Error");
    }

    // SMS, Email は使えないのでここでトークンを取得して
    // Account/VerifyCode に渡すコードを追加
    var id = await SignInManager.GetVerifiedUserIdAsync();
    var provider = model.SelectedProvider;
    var code = await UserManager.GenerateTwoFactorTokenAsync(id, provider);

    // パラメータ token という名前で Account/VerifyCode
    // にトークンを渡す
    return RedirectToAction("VerifyCode", 
        new { 
            Provider = model.SelectedProvider, 
            ReturnUrl = model.ReturnUrl, 
            RememberMe = model.RememberMe,
            Token = code        // これを追加
        });
}

上のコードの最後で Accout/VerifyCode にリダイレクトしていますが、そこで取得したトークンを Token という名前のパラメータとして設定して渡します。Accout/VerifyCode アクションメソッドの引数でパラメータ(クエリ文字列)Token に設定されたトークンを取得し、ViewBag を使って View に渡して表示しています。

Tags: , , ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  October 2021  >>
MoTuWeThFrSaSu
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

View posts in large calendar