LoginSignup
17
18

【画像解説】ASP.NET Core MVCに認証機能Identityを実装する~ログインからメール検証機能まで~

Last updated at Posted at 2022-04-15

前書き

ASP.NET Core Identityってなんなん

ASP.NET Core Identityはユーザー認証機能をサポートするAPIです。

もっと雑に言うと、
「ログイン機能の既製品」という感じです。

・アカウント登録
・ログイン
・ログアウト
・閲覧制限

などの単純な機能だけでなく、

・アカウントを有効化する機能
・パスワードを忘れたら再設定する機能
・Twitter、Google、FaceBookのアカウントでログインできる機能

などなど本格的な機能も用意してくれています。

さらに中身的なことだけでなく、UIテンプレートまで用意していくれているので
イチからHTMLを書いて画面をデザインする手間も省けます。

Identityはテンプレートをいじれるようになっています。
特に、本記事で紹介するメール検証機能を実装できたあかつきには
多少は自由にカスタマイズできるレベルになると思います。

既存プロジェクトにパパッとIdentity実装したいのだが

既存プロジェクトに後からIdentityを実装する方法ですが...
現在まだ楽な方法を見つけられておりません…。

私はネット先人たちの見よう見まねで
・Identityを半分ぐらい使いつつ半分ぐらい自前で実装する
・Identityを全く使わずに完全に自前実装する
・新規のIdentityプロジェクトに既存プロジェクトのデータをマージする

などを試しましたが、
ピヨグラマ的に一番マシなのは3つ目の方法でしたね…。

開発は計画的に。

環境

Windows10
Visual Studio 2022 Community
Microsoft SQL Server 2019
.NET 6.0
C# 10.0

目次

この記事では
Identityのテンプレートを利用し、

【STEP1】アカウント登録/ログイン/ログアウト機能を実装する
─最も基本の認証機能を作る
【STEP2】ページに閲覧制限をかける
─ログインしてからでないと閲覧できないページを作る
【STEP3】メール検証機能を実装する
─登録したメールアドレスに検証リンクを送信する機能を作る

というところまで、手を動かせば初心者でもできるように説明します。

時間のない方や、仕組みが理解できなくていいからとりあえず手を動かして作ってみたい方は
これ以降、この緑の枠内は読み飛ばしてください。

では早速実装していきましょう。

【STEP1】アカウント登録/ログイン/ログアウト機能を実装する

[1/3] 新規プロジェクト作成時に"認証の種類"を選択する

Identityをフル実装するには最初が肝心です。
とはいえプロジェクト作成時に1か所設定を変更するだけ。

まず、ASP.NET Core Webアプリ プロジェクトを作成します。
1プロジェクト作成.png

認証の種類で「個別のアカウント」を選択します。
2認証の種類を選択.png
普段スルーしてたけどこんなところでIdentityを適用できたのかー。

[2/3] マイグレーションする

.NETにはEntityFramework(略称:EF)という、オブジェクトをDBに反映してくれる便利なライブラリが存在します。
EFのおかげで生SQLを書くことなく、専用メソッドを組み合わせてCRUD操作できたり、クラス定義がそのままテーブル設計になってくれたりします。
ソースコードだけでDB設計が行える機能(技術)をコードファーストといい、DB内のデータを保ったままテーブル設計に変更を加える機能(技術)をマイグレーションといいます。

ここではEFのコードファストでマイグレーションをし、Identityに必要なDBを生成します。
[1/3]で既にEFのパッケージは入っているので何もしなくてOKです。

まずはパッケージマネージャーコンソールを表示しておきましょう。
PMCの出し方.png

パッケージマネージャーコンソール(略称:PMC)は、VisualStudio内のPowerShellコンソールです。
名前の通り、主に.NET FrameworkのパッケージマネージャーであるNuGetの代替手段として、パッケージのインストール、アンインストール、アップデートに使われます。
しかしそれだけでなく、多くのパッケージがPMCコマンドを持っており、例えば今回のようにEntityFrameworkの機能を利用するときにもPMCを使います。

PMCに以下のコマンドを入力し実行します。

PMC
update-database

この時PMCにブワーーーっとCREATE TABLE文等のSQLが流れていきます。
これは、Data>Migrationsフォルダ内の00000000000000_CreateIdentitySchema.cs内に定義されているCreateIdentitySchemaクラスから発行されています。
maigure.png

これでDBが生成されました。
デフォルトではSQL Serverのローカルに作成されるようです。
接続文字列はsettings.jsonにあります。

生成されたDBを確認すると、下の画像の赤枠内と同様のテーブルができているはずです。
AspNetから始まるものはIdentityで使うテーブルです。
__EFMigrationHistoryはマイグレーションの反映履歴を記録するテーブルです。
DB中身確認.png

これでSTEP1でやることは終了です!

[3/3] 動作確認

アプリケーションをデバッグ実行し、アカウント登録、ログイン、ログアウト機能が正常に動作することを確認しましょう。

アカウント登録

登録すると、Register confirmation画面 に遷移します。
RegisterConfirm.png
"Click here to confirm your account"をクリックして検証を完了させます。
ConfirmEmail.png
※本来はRegister Confirmationの画面に飛んだ時点で、登録したメールアドレスに検証メールが届きます。
届いたメールのリンク先に飛ぶことで検証が完了し、ログインができるようになる仕組み…なのですが このメール検証機能は、自分で実装しないといけません。(【STEP3】で実装します)
代わりに先ほどの"Click here to confirm your account"をクリックすることでメール検証機能が未実装の状態でもアカウント登録~ログインの流れを試せるようになっています。

ログイン

ログインに成功するとナビゲーションバーに"Hello [登録したメールアドレス]!"と表示されます。
ログイン成功.png

ログアウト

ナビゲーションバーの"Logout"からログアウトします。

以上でアカウント登録・ログイン・ログアウトの一連の流れを試すことができました。
でも、現状はログインしていなくてもすべての画面を閲覧できるのであまり実感が湧きませんね。
ということで次のSTEPです。

【STEP2】ページに閲覧制限をかける

[1/1]アノテーションを追記する

コントローラークラスまたはコントローラー内のメソッドに以下のように[Authorize]アノテーションを追記するだけで、任意のページに閲覧制限をかけることができます。

▼コントローラー単位で制限をかける例(HomeとPrivacyに閲覧制限がかかります)

HomeController
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MyIdentityApp.Models;
using System.Diagnostics;

namespace MyIdentityApp.Controllers
{
    [Authorize]
    public class HomeController : Controller
    {
       //以下省略

▼メソッド単位で閲覧制限をかける例(Privacyにのみ閲覧制限がかかります)

HomeController
 [Authorize]
        public IActionResult Privacy()
        {
            return View();
        }

デバッグ実行すると、今度はログイン画面に転送されるようになりました。
ナビゲーションバーから各ページへ飛ぼうとしても同様にログインを求められます。

【STEP3】メール検証機能を実装する

このステップでは、アカウント初回登録時やパスワード再設定時などに、登録したメールアドレスに検証リンクを送信する機能を作ります!

手順が多い…と思われるかもしれませんが、とりあえず脳死状態でコピペとテンプレの微修正すれば初心者でも完成するのでご安心ください。

[1/8] SendGridに無料登録しAPIKeyを取得する

今回はSendGridというメールプロバイダーのメール送信機能を使ってメールを送信します。
MicroSoft様いわくSMTPを使いこなすのは難しいからこういう外部サービス使うほうがオススメのようです。
SendGridは顧客とのメールのやり取りを通じてマーケティングまでできちゃうクラウドサービスとのこと。
まあ今回は送信機能しか使いませんが。

SendGridではフリーメールが使えないようなので、独自ドメインのメールアドレスを用意してください。

アプリケーションからSendGridを利用するためにAPIKeyを取得します。

まずはSendGridの指示に従い、アカウント登録・初期設定を済ませておいてください。
なんやかんやで私は20分ぐらいかかりました。

次に、画面左側のメニューバーからSettings→APIKeysをクリックします。
yokoMenu.png

Create API Key をクリックします。
createApiKey.png

APIKeyに任意の名前をつけて Create & View をクリックします。
createApiKey2.png

APIKeyが作成されました。(下画像のモザイク箇所)
画面にも記載のある通り、セキュリティの観点から二度と表示されないのでこの場でメモしてください。後で使います。
もし紛失した場合、APIKeyを新たに作り直すことは可能です。
createApiKey3.png

[2/8] SendGridパッケージのインストール

プログラムとSendGridが連携できるようにします。
PMCで以下のコマンドを実行します。

PMC
install-package SendGrid

[3/8] ユーザーシークレットの設定

ユーザーシークレットとは、"開発用コンピューターで ASP.NET Core アプリ用の機密データを管理できるヤツ"としてMSが推奨しているヤツです。
機密データをソースコードに直接書いちゃったら(ハードコーディング)ヤバイよ!ってことでローカルのある場所に格納しておき、プログラムが取ってきて使う仕組みです。

今回はSendGridのAPIKeyが機密データにあたるので、これをユーザーシークレットに入れます。

まずはユーザーシークレットを表示します。
ソリューションエクスプローラーのプロジェクト上で右クリック→ユーザーシークレットの管理(G) を選択します。
ユーザーシークレットの管理.png
secret.jsonが表示されるので、[1/8]で取得したAPIKeyを格納します。

secret.json
{
  "SendGridKey": "ここをご自身のAPIKeyに書き換えてください"
}

ちなみにユーザーシークレットが格納される場所は以下です。
%APPDATA%\Microsoft\UserSecrets

[4/8] AuthMessageSenderOptionsクラス実装

[3/8]でユーザーシークレットに格納したデータを保持するクラスを実装します。

プロジェクト直下にServiceフォルダを作成し、AuthMessageSenderOptions.csを追加します。
以下の通りにコードを記述します。

AuthMessageSenderOptions.cs
namespace MyIdentityApp.Services
{
    public class AuthMessageSenderOptions
    {
        public string SendGridKey { get; set; }
    }
}

[5/8] EmailSenderクラス実装

このSTEPのメインパート。
実際にメール送信を行うクラスです。
以下のようにコードを記述します。
※メールアドレスと送信者名はご自身のものに書き換えてください。

using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace MyIdentityApp.Services;

public class EmailSender : IEmailSender
{
    private readonly ILogger _logger;

    public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
                       ILogger<EmailSender> logger)
    {
        Options = optionsAccessor.Value;
        _logger = logger;
    }

    public AuthMessageSenderOptions Options { get; } //Set with Secret Manager.

    public async Task SendEmailAsync(string toEmail, string subject, string message)
    {
        if (string.IsNullOrEmpty(Options.SendGridKey))
        {
            throw new Exception("Null SendGridKey");
        }
        await Execute(Options.SendGridKey, subject, message, toEmail);
    }

    public async Task Execute(string apiKey, string subject, string message, string toEmail)
    {
        var client = new SendGridClient(apiKey);
        var msg = new SendGridMessage()
        {
            From = new EmailAddress("an-nin@dooofuuu.com", "送信者名"), // 引数はご自身のものに書き換えてください。
            Subject = subject,
            PlainTextContent = message,
            HtmlContent = message
        };
        msg.AddTo(new EmailAddress(toEmail));

        // Disable click tracking.
        // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
        msg.SetClickTracking(false, false);
        var response = await client.SendEmailAsync(msg);
        _logger.LogInformation(response.IsSuccessStatusCode
                               ? $"Email to {toEmail} queued successfully!"
                               : $"Failure Email to {toEmail}");
    }
}

[6/8] Program.csでDIする

まず、Program.cs内に以下のusingを追記します。

Program.cs
using Microsoft.AspNetCore.Identity.UI.Services;
using MyIdentityApp.Services;

続いて、var app = builder.Build(); の手前に以下のコードを追記します。
これでクライアントにIEmailSender型のオブジェクトを渡せるようになりました。

Program.cs
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

// var app = builder.Build();

Dependency Injection(略称:DI)とは、和訳すると"依存性の注入"です。(日本語なのに意味がわからん)

あるオブジェクトAが別のオブジェクトBを使うとき(AがBに依存するとき)、呼び出されるBのプログラムに問題があれば呼び出し元のAの動作にも影響を及ぼす可能性があります。このとき、何も考えなければAのプログラム内でBをインスタンス化すると思いますが、問題はAだけの動作確認(単体テスト)をしたいときです。Aを呼ぶと必ずBも一緒に呼ばれるため、正しく動いているように見えてもそれは偶然Bが暗躍したからかもしれないし、誤動作が起こっても犯人がAなのかBなのか特定しづらいです。

DIとやらをしていれば単体テストがしやすくなります。
概要としては、Aとは別の場所(外部)でBオブジェクトを生成し、Aに渡します。
Aは渡されたBオブジェクトを使うだけになります。

Aの単体テスト時は、テストクラスでモックというBのハリボテを生成し、Aに渡します。
ハリボテは中身がすっからかんなので、もしテストに失敗すれば必ず原因はAと特定できます。

また、Aのような使う側をクライアント、Bのような使われる側をサービスといいます。

まとめると、DIとは依存性を外部から注入するデザインパターンです。(雀の涙ぐらいマシになったか)

DIでサービスを受け渡す方法はいくつかありますが、今回はProgram.cs(旧Startup.cs)内でサービスオブジェクトを生成し、これをクライアントがコンストラクタの引数で受け取るというコンストラクタ・インジェクションが採用されています。

Identityが定義しているRegister.cshtml.cs等の複数クラスで、メール送信を行うためにIEmailSender型オブジェクトをコンストラクタの引数で受け取るようにコードが書かれています。(このコードを見たい方は後に説明するスキャフォールドをしてください)

今回、各プログラムの関係性は以下のようになっています。

  • クライアント(使う側):Register.cshtml.cs等、メール送信指示を出す複数のクラス
  • サービス(使われる側):IEmailSenderの実装である、実際にメール送信を行うEmailSenderクラス
  • 外部(サービスオブジェクトを用意する場所):Program.cs内

以上で中身は完成しました!
残りは見た目に微修正を加えるだけです。

[7/8] スキャフォールドしてUIテンプレートのソースコードを取得する

スキャフォールド機能とは、テンプレソースを自動生成してくれる便利機能のことです。
イチからコードを書く必要がなく、自動生成されたテンプレをいじってカスタマイズしていけば効率良く開発ができる。まるで全自動食洗器やドラム式洗濯機みたいなマジでナイスなヤツです。

今回は[8/8]のための準備でスキャフォールドします。

【STEP1】で新規プロジェクトを作成した時点で、ユーザー認証に関係するUIや基本機能は既に完成していましたが、メール検証機能を実装するにあたってUIに変更を加えます。
Identityが隠し持っているソース自体をいじることはできないのですが、代わりにスキャフォールディングによってそっくりさんたち(オーバーライド)を生成し、それらをいじることでIdentityをカスタマイズできる仕組みです。

以下の手順でスキャフォールドを行います。

ソリューションエクスプローラーのプロジェクト上で右クリック→追加(D)→新規スキャフォールディングアイテム(F) を選択します。
スキャ1.png

画面左側のIDを選択→画面中央のIDが選択されていることを確認→画面右下の追加をクリックします。
スキャ2.png

スキャフォールディングするファイルを選択します。
最低限は今回編集するAccount¥RegisterConfirmにチェックを入れればOKです。
また、[5/8]で実装したEmailSenderを実際に使っているコードを見てみたければ、Account¥Registerにもチェックを入れてみてください(※他にも数クラスあります)。そこではコンストラクタの引数でIEmail型オブジェクトを受け取り、SendEmailAsyncメソッドを呼び出しています。

スキャ3.png
Areas>Identity>Pages>Accountに選択したファイルが現れました。
スキャ4.png

[8/8] RegisterConfirm.cshtml.csの編集

【STEP1】[3/3]の注意書きの通り、メール送信機能が実装されていない状態でもアカウント登録機能を試せるように、Register confirmation画面には検証用リンクが表示されていました。
[6/8]の時点で機能自体は完成済なので、これを非表示にしましょう。
RegisterConfirm - コピー.png

RegisterConfirm.cshtml.csファイルを開き、47行目あたりのOnGetAsync()メソッド内を編集します。
"// Once you add a real email sender, you should remove this code that lets you confirm the account"
というコメントに従い、62行目から一連のコードを削除します。

RegisterConfirmation.cshtml.cs 削除箇所抜粋
 //Once you add a real email sender, you should remove this code that lets you confirm the account
           DisplayConfirmAccountLink = true;
            if (DisplayConfirmAccountLink)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                EmailConfirmationUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                    protocol: Request.Scheme);
            }

代わりに以下のコードに書き換えます。

RegisterConfirmation.cshtml.cs
DisplayConfirmAccountLink = false;

これで検証リンクが非表示になりました。
RegisterConfirmation後.png

新しいアカウントを作ったり、パスワードを忘れたふりして再発行してみましょう。
アカウント登録したメールアドレスに検証メールが飛べば成功です!

新規アカウント登録時

リンク先に飛ぶと検証フラグが立ちます。
(AspNetUserテーブルのEmailConfirmedがtrueになる)
これをもってアカウントが有効化され、ログイン可能な状態になります。
登録時メール.jpg

パスワード再設定時

リンク先に飛ぶと新しいパスワードを設定できます。
pw再設定時メール.jpg

終わりに

手を動かすだけならそれほど苦労なく実装できたかと思いますが、
多少の仕組みまで理解するとなれば初心者には勉強になることがたくさんあったはずです。

また、今後Identityをカスタマイズしたい方にとって、
【STEP3】のメール検証機能の実装ではいろいろ収穫があったのではないでしょうか。

今後もC#.NETでの開発を楽しみましょう!
※そのうち本記事の情報量を増やしたり減らしたりする可能性があります。

17
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
18