WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

プロファイル情報の追加 (CORE 版)

by WebSurfer 23. May 2020 14:09

ASP.NET Core 3.1 MVC アプリに ASP.NET Core Identity ベースのユーザー認証を実装して、プロファイル情報(カスタムユーザーデータ)としてハンドル名を追加し、ログイン時にページのヘッダ右上にデフォルトで表示される Email に代えてハンドル名を表示する方法を書きます。

ハンドル名の表示

プロファイル情報を追加するには Microsoft のドキュメント Add, download, and delete custom user data to Identity in an ASP.NET Core project の Run the Identity scaffolder セクションに書いてあるように、IdentityUser を継承した xxxxxUser クラス(xxxxx はデフォルトではプロジェクト名)を作ってそれを使う必要があります。

そのようなプロジェクトの作成手順は、先の記事「ASP.NET Identity で MySQL 利用 (CORE 版)」のステップ「(1) プロジェクトの作成」とステップ「(2) ASP.NET Core Identity の実装」を見てください。

以下の説明は Visual Studio 2019 でプロジェクト名を MySQLIdentity として作成した ASP.NET Core 3.1 MVC アプリをベースにしています。そのプロジェクトは、記事に書いたとおりデータベースは MySQL を使うように変更していますが、以下のプロファイル情報の追加手順は SQL Server でも同じです。

(1) HandleName プロパティの追加

プロジェクトの Areas/Identity/Data/MySQLIdentityUser.cs ファイルに IdentityUser を継承した MySQLIdentityUser クラスが定義されています(注: MySQLIdentity の部分はプロジェクト名になります。すなわちプロジェクトによって変わります)

プロジェクト作成時点では MySQLIdentityUser クラスの中身は空です。それに HandleName プロパティを追加します。以下のような感じです。

using Microsoft.AspNetCore.Identity;

// 追加
using System.ComponentModel.DataAnnotations;

namespace MySQLIdentity.Areas.Identity.Data
{
    public class MySQLIdentityUser : IdentityUser
    {
        // プロファイル情報としてハンドル名を追加
        [PersonalData]
        [Display(Name = "ハンドル名")]
        [Required(ErrorMessage = "{0}は必須です。")]
        [StringLength(128, ErrorMessage = "{0}は{1}文字以内で入力してください。")]
        public string HandleName { get; set; }
    }
}

そのあと Migration 操作を行うと Entity Framework Code First の機能によりデータベースには HandleName フィールドが追加されます。HandleName フィールドはデータアノテーション属性の Required により NULL 不可に、StringLength の引数 128 により varchar(128) になります。

後で気が付いたのですが、表示名や検証のエラーメッセージは Razor ページで別に定義したモデルに付与した属性のものを使うので、ここで上のように DisplayName 属性やエラーメッセージを付与しても意味はなさそうです。しかし、害もなさそうなのでそのままにしています。

PersonalData 属性は、参考にした Microsoft のドキュメントによると "it's automatically available for download and deletion. Making the data able to be downloaded and deleted helps meet GDPR requirements." とのことです。

その "downloaded" というのは Manage ページでユーザー情報の JSON 文字列を PersonalData.json という名前のファイルにしてダウンロードする機能で、その中にハンドル名のデータが含まれるようになります。PersonalData 属性を付与しないとプロファイル情報は含まれないのかは未検証です。

"deleted" はユーザー情報がすべてデータベースから削除されるという動きになります。PersonalData 属性を付与しようがしまいが全削除には変わりはないと思われるのですが・・・

(2) Add-Migration

Visual Studio のパッケージマネージャーコンソールで Add-Migration CustomUserData コマンドを実行します。CustomUserData という名前は任意です。

成功すると Migrations フォルダに xxxxx_CustomUserData.cs という名前(xxxxx は作成日時)のクラスファイルが生成され、それに以下のようなコードが含まれているはずです。

using Microsoft.EntityFrameworkCore.Migrations;

namespace MySQLIdentity.Migrations
{
    public partial class CustomUserData : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AddColumn<string>(
                name: "HandleName",
                table: "AspNetUsers",
                maxLength: 128,
                nullable: false,
                defaultValue: "");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropColumn(
                name: "HandleName",
                table: "AspNetUsers");
        }
    }
}

上のコードは、既にデータベースには ASP.NET Core Identity 用のテーブルはすべて生成済みという状態にプロファイル情報 HandleName を追加した場合です。プロジェクト作成直後のデータベースには何もない状態で Add-Migration を実行した場合に生成されるコートとは異なります。

(3) Update-Database

Visual Studio のパッケージマネージャーコンソールで Update-Database コマンドを実行します。成功すると ASP.NET Core Identity 用のデータベースの aspnetusers テーブルに HandleName フィールドが追加されます。

aspnetusers テーブル

この記事ではデータベースは SQL Server ではなく MySQL を利用しています。nvarchar でなく varchar となっているのは MySQL の事情です。

MySQL のドキュメント 10.3.7 The National Character Set によると、"MySQL uses utf8 as this predefined character set" なのでどちらでも同じなのだそうです。ちなみに create 句で nvarchar と指定しても、生成されるのは varchar になります。

(4) Register ページの修正

Areas/Identity/Pages/Account/Reguster.cshtml.cs の OnPostAsync メソッドに、以下の画像の赤枠のコードを追加します。登録時にはハンドル名は自動的に Email と同じになるようにするものです。

Register ページの修正

上のステップ (1) で HandleName プロパティには Rquired 属性を付与しましたので、aspnetusers テーブルの HandleName フィールドは NULL 不可になっています。ステップ (3) の画像を見てください。

そのため、新規ユーザーの登録を行う際 HandleName を入力しないと CreateAsync でエラーとなります。と言って、登録時の手間を増やすとユーザーに面倒がられると思われます。

なので、登録時には自動的に HandleName は Email と同じになるようにし、後で変更できるようにしました。

(5) ハンドル名の変更機能を実装

ログイン後、ページのヘッダの右上のユーザー名(デフォルトで Email)をクリックすると Manage ページに遷移しますが、そこでユーザーがハンドル名を任意に変更できるようにします。

まず Areas/Identity/Pages/Account/Manage/Index.cshtml.cs の InputModel クラスへのハンドル名用のプロパティの追加と、LoadAsync ヘルパーメソッドでのハンドル名の InputModel への設定のためのコードを追加します。入力できるハンドル名の長さはとりあえず 30 文字に制限してみました(当該 input 要素に maxlength="30" が設定されます)。

InputModel クラスの処理

同じく Index.cshtml.cs のコードの OnPostAsync メソッドにハンドル名の変更をデータベースに反映するコードを追加します。

ハンドル名の変更処理

次に Areas/Identity/Pages/Account/Manage/Index.cshtml にハンドル名入力用のテキストボックスを追加します。

ハンドル名用テキストボックス追加

(6) ハンドル名の変更操作

ユーザーがログインするとページのヘッダの右上に Hello xxxxx! と表示され、それが Manage ページへのリンクになります。それをクリックするとプロファイル情報の編集画面に遷移します。

上のステップ (5) で加えたコードの修正により、画面にはハンドル名入力用のテキストボックスが表示されています。

ハンドル名の変更

初期画面では[ハンドル名]テキストボックスには[Username]テキストボックスと同じメールアドレスが表示されます。それを任意の名前に設定し[Save]ボタンをクリックするとハンドル名は変更されます。

上の画像は初期画面のメールアドレスを WebSurfer というハンドル名に変更した後のものです。

(7) ClaimsIdentity へ追加

ページのヘッダの右上に Hello xxxxx! と表示される xxxxx にはデフォルトではメールアドレスが表示されていますが(上のステップ (6) の画像参照)、それをハンドル名に変更します。結果、この記事の一番上の画像のようになります。

プロファイル情報を Claim として ClaimsIdentity オブジェクトに追加し、拡張メソッドを使って ClaimsIdentity オブジェクトからハンドル名を取得・表示するようにしています。

ClaimsIdentity オブジェクトにハンドル名を追加すると、認証クッキーにハンドル名が含まれるようになります。認証クッキーからハンドル名を取得できれば、毎回データベースにクエリを投げて取得するより負荷は軽い(であろう)というのが ClaimsIdentity を使う理由です。

プロファイル情報を追加・取得するための詳しい手順は、先の記事「ASP.NET Core MVC の ClaimsIdentity」に書きましたので見てください。

注意すべき変更点は、(1) その記事は PhoneNumber を使っていますが、それを HandleName に変更、(2) IdentityUser を MySQLIdentity.Areas.Identity.Data 名前空間の MySQLIdentityUser に変更、(3) ClaimTypes の HomePhone を GivenName に変更(GivenName でなくても良いですが Name はすでに使われているようで NG)・・・です。

Tags: , ,

CORE

Password Recovery (CORE 版)

by WebSurfer 19. May 2020 12:59

Password Recovery はユーザーがパスワードを忘れてしまっても復活できる手段を提供するものです。ASP.NET Core 3.1 の Password Recovery がデフォルトの実装ではどのような動きになるかを以下に説明します。

(先の記事「Email Confirmation の実装 (CORE 版)」の「(9) Password Recovery」の補足です)

ページのヘッダ右上に表示されている [Login] をクリックして Login ページを表示すると、その中に [Forgot your password?] というリンクがあります。それをクリックすると下の画像の ForgotPassword ページに遷移します。

ForgotPassword ページ

その Email 欄にメールアドレスを入力して [Submit] をクリックすると、ForgotPassword.cshtml.cs の OnPostAsync メソッドで (a) 送信されたメールアドレスが登録済み、(b)Email Confirmation 済みであることを確認します。

条件 (a), (b) が確認できない場合は単純に ForgotPasswordConfirmation ページにリダイレクトされるだけでそれ以外何も起こりません。

条件 (a), (b) の条件の両方が確認できるとメールが送信され、その後で ForgotPasswordConfirmation ページにリダイレクトされます。以下のような画面になります。ユーザーにメールを見るよう促しているだけです。

ForgotPasswordConfirmation ページ

送信されたメールの本文には ResetPassword ページに確認用のトークンをクエリ文字列に含めた URL が含まれます。

Reset Password メール

上の画像のメールは、ユーザーのメーラーが html の表示を許可していない可能性を考えて URL そのものを送信するようにしています。(自動生成されるコードでは、URL を a 要素の href 属性に設定したハイパーリンク "clicking here" になります)

具体的には、Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs の OnPostAsynce() メソッドの中の SendEmailAsync メソッドのコードを以下のようにしています。

await _emailSender.SendEmailAsync(
    Input.Email,
    "Reset Password",
    "Please confirm your account by: " + 
        HtmlEncoder.Default.Encode(callbackUrl));

メールの URL をクリックすると ResetPassword ページが GET 要求され、クエリ文字列から確認用のトークンが渡されます。その応答としてブラウザに以下のような画面が表示されます。

ResetPassword ページ

上のページに、最初に ForgotPassword ページで申請したのと同じメールアドレスと、新しいパスワードを入力して [Submit] をクリックすると、メールアドレスとトークンが有効であればパスワードが新しいものに変更され、その後 ResetPasswordConfirmation ページにリダイレクトされます。

ResetPasswordConfirmation ページ

そのページのリンク [click here to log in] をクリックすると Login ページに遷移するので、そこでメールアドレスと新しいパスワードを入力すればログインできます。


ユーザー登録の場合と違って、Password Recovery ではメールが受信でき、それに含まれるトークンを ResetPassword ページに送信し、そのページで新しいパスワードを登録できないと何ともならない・・・すなわちメール送信機能の実装は必須のコーディングとなっています。

メール送信機能を実装しなくても Password Recovery を可能にするには、コードを書き換えて、 ForgotPasswordConfirmation ページで上に書いた条件 (a), (b) の条件の両方が確認できたら、ResetPassword ページにリダイレクトするといった修正が必要と思われます。(未検証・未確認です)

Tags: , , ,

CORE

Email Confirmation の実装 (CORE 版)

by WebSurfer 18. May 2020 16:12

ASP.NET Core 3.1 MVC で ASP.NET Identity をユーザー認証に使用し、Email Confirmation と Password Recovery を実装する際、自分的に気になったことを備忘録として書いておきます。

(.NET Framework 版については特に気を付ける点はなさそうなので割愛します。手抜きですみません。基本的な枠組みが Visual Studio のテンプレートで生成されるコードに含まれており、Microsoft のチュートリアル「ログイン、電子メール確認、パスワード リセットを使用して安全な ASP.NET MVC 5 Web アプリを作成する (C#)」に従って生成されたコードに手を加えれば特に苦労なく実装できると思います)

基本的には Microsoft のチュートリアル Account confirmation and password recovery in ASP.NET Core に従って実装しますので、詳しくはそのドキュメントを見てください。そのドキュメントの説明だけではよく分からないと思った点を以下に書きます。

(1) プロジェクトの作成

Visual Studio 2019 のテンプレートを利用して ASP.NET Core 3.1 MVC アプリを作成します。

認証関係のソースコードに手を加えるので、アプリ作成の際に認証は「認証なし」のままとしておき、スキャフォールディング機能を利用して ASP.NET Identity を実装し、ソースコードがプロジェクトに含まれるようにします。

詳しい手順は先の記事「ASP.NET Identity で MySQL 利用 (CORE 版)」のステップ「(1) プロジェクトの作成」とステップ「(2) ASP.NET Core Identity の実装」を見てください。

(2) Email Confirmation 実装前の動き

上のステップ (1) でプロジェクトを作成直後(Email Confirmation を実装する前)のユーザー登録の仕組み説明します。それを理解しておいた方がこの後の説明が分かりやすいと思いますので。仕組みをご存じの方はここはスキップして (3) に進んでください。

デフォルトで options.SignIn.RequireConfirmedAccount が true に設定されており(そのコードは Areas/Identity/IdentityHostingStartup.cs にあります)、ユーザー登録に使用する Register ページも Email Confirmation を使用する前提でコーディングされています。ちなみに false に設定すると登録後直ちにログイン状態になります。

Areas/Identity/Pages/Account フォルダにある Register.cshtml.cs を開いて OnPostAsync メソッドのコードを見てください。登録画面にユーザーが email と password を入力して送信するとそのメソッドでユーザー登録処理が行われます。

ユーザー登録に成功すると RegisterConfirmation ページにリダイレクトされます。(実際はリダイレクト前に _emailSender.SendEmailAsync メソッドが実行されますが、デフォルトではメール送信機能は実装されていないので何も起こりません。何故かエラーも出ません)

RegisterConfirmation ページは下の画像のように表示されます。

RegisterConfirmation ページ

上の画像の "Click here to confirm your account" には ConfirmEmail ページへのリンクが張ってあり、クエリ文字列に UserId とトークンが設定されています。

リンクをクリックすると ConfirmEmail ページが GET 要求され Areas/Identity/Pages/Account フォルダにある ConfirmEmail.cshtml.cs の _userManager.ConfirmEmailAsync(user, code) メソッドで Email Confirmation が実行(AspNetUsers テーブルの EmailConfirmed フィールドが True に更新)されます。

Email Confirmation に成功すると下の画像の ConfirmEmail ページが表示されます。

ConfirmEmail ページ

この後、右上の [Login] をクリックして Login ページを表示し、登録した email と password を入力してログインできるようになります。

Email Confirmation を実装してメールを送信できるようにすると、ConfirmEmail ページへのリンク(UserId とトークンのクエリ文字列を含む)が本文に含まれたメールが自動的に送信され、それをクリックすると ConfirmEmail ページが GET 要求されて Email Confirmation が実行されるようになります。

RegisterConfirmation ページへのリダイレクトは同様に起こりますが、それには ConfirmEmail ページへのリンクは含まれず、単に "Please check your email to confirm your account." と表示されただけものになります。

以下のステップ (3) 以降にメールの自動送信を含む Email Confirmation の実装方法を述べます

(3) API Key ���入手

チュートリアルに従って、メールの送信には SendGrid を利用します。ユーザー登録して API Key を入手してください。

API Key の入手

SendGrid は基本的に有償ですが、開発目的限定で送信する量が少ない場合は Free のサービスでよさそうです。Pricing のページを見てください。

(4) SendGrid のインストール

NuGet を利用してプロジェクトに SendGrid をインストールします。

SendGrid をインストール

上の画像ではバージョンが 9.12.6 となっていますが、自分が試したときはそれが最新版だったということで、意図的にそのバージョンを選んだわけではありません。

(5) User Secrets

メールの差出人と SendGrid の API Key をユーザーシークレットに設定します。チュートリアルは dotnet コマンドを使っていますが、Visual Studio のソリューションエクスプローラーから開いて設定できます。

ユーザーシークレットの管理

上の画像のように操作すると secrets.json ファイルが開くので、それに以下のようにメールの差出人と SendGrid の API Key を設定して保存します。

secrets.json

普通に appsettings.json に設定しても取得できます。機密性の高い情報を開発時にどこに保存するかのチュートリアルの意味でユーザーシークレットを紹介しているのかもしれません。

ユーザーシークレットは開発時の開発マシンのでの利用に限られます。secrets.json ファイルは開発マシンのローカルフォルダにあるので当たり前ですが・・・ 詳しくは Safe storage of app secrets in development in ASP.NET Core を見てください。

チュートリアルには AuthMessageSenderOptions クラスの追加と Startup.ConfigureServices へのコードの追加を行うように書かれています。

まず、プロジェクトルート直下に Services フォルダを追加し、その中にクラスファイル AuthMesageSenderOptions.cs を追加します。コードはチュートリアルのものをコピペします。

// AuthMessageSenderOptions クラスの追加

namespace MySQLIdentity.Services
{
    public class AuthMessageSenderOptions
    {
        public string SendGridUser { get; set; }
        public string SendGridKey { get; set; }
    }
}

次に、Startup.cs の ConfigureServices メソッド内に以下のコードを追加します。

// Startup,cs の ConfigureServices メソッド内に追加

services.Configure<AuthMessageSenderOptions>(Configuration);

これらは、AuthMessageSenderOption クラスを、必要に応じて初期化してそのオブジェクトを DI によって注入するために必要なもののようです。

その設定により、secrets.json ファイルに設定した差出人と API Key 情報は、EmailSender クラスを初期化する際に DI によって注入された AuthMessageSenderOption オブジェクトから取得できます。

なお、secrets.json ファイルを使わなくても、普通に appsettings.json に差出人と API Key 情報を設定しても同様に DI によって取得できます(secrets.json と appsettings.json の両方から探してくるようです)。運用環境にデプロイする際に appsettings.json に移すということではなさそうですが、実際どうすべきかは調べておらず不明です。

(6) EmailSender クラスの実装

メールを送信するため IEmailSender インターフェイスを継承した EmailSender クラスを実装します。

チュートリアルに従ってクラスファイル Services/EmailSender.cs を追加し、それにチュートリアルのコードをそのままコピペしてください。

念のため、以下にコードを貼っておきます。

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

namespace MySQLIdentity.Services
{
    public class EmailSender : IEmailSender
    {
        public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
        {
            Options = optionsAccessor.Value;
        }

        public AuthMessageSenderOptions Options { get; }

        public Task SendEmailAsync(string email, string subject, string message)
        {
            return Execute(Options.SendGridKey, subject, message, email);
        }

        public Task Execute(string apiKey, string subject, string message, string email)
        {
            var client = new SendGridClient(apiKey);
            var msg = new SendGridMessage()
            {
                From = new EmailAddress("xxxxx", Options.SendGridUser),
                Subject = subject,
                PlainTextContent = message,
                HtmlContent = message
            };
            msg.AddTo(new EmailAddress(email));

            // Disable click tracking.
            // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
            msg.SetClickTracking(false, false);

            return client.SendEmailAsync(msg);
        }
    }
}

上のコード From = new EmailAddress("xxxxx", Options.SendGridUser) の xxxxx をメールの差出人のメールアドレスに変更してください。

EmailSender クラスが初期化される際に DI により AuthMessageSenderOptions オブジェクトが注入され、差出人と API Key 情報が取得できるようにコーディングされています。

EmailSender クラスは Register ページと ForgotPassword ページでメールを送信する際に使用されます。その際、DI により EmailSender クラスを初期化してそのオブジェクトを注入するコーディングになっています。それを可能にするため、Startup.cs の ConfigureServices メソッド内に以下のコードを追加します。

// Startup.cs の ConfigureServices メソッド内に追加

services.AddTransient<IEmailSender, EmailSender>();

上記の Startup.cs への設定を忘れるとメール送信機能は動きませんので注意してください。

(7) Register ページの修正

Areas/Identity/Pages/Account フォルダの Register.cshtml.cs を開いて OnPostAsync メソッドのコードを見てください。

ユーザー登録に成功すると変数 callbackUrl に UserId とトークン情報がクエリ文字列として設定された /Account/ConfirmEmail ページへの URL 文字列が設定されます。

デフォルトでは html の a 要素の href 属性にその URL が設定されたメール本文がユーザーが email として登録したメールアドレスに送信されます。

ユーザーが使っているメーラーが html を表示できる設定になっていれば、その a 要素で "clicking here" と表示されるリンクをクリックすれば ConfirmEmail ページが GET 要求され、クエリ文字列に設定された UserId とトークンで Email Confirmation が行われ、ブラウザが立ち上がって ConfirmEmail ページが表示されます。

ユーザーのメーラーが html の表示を許可していない可能性を考えて、html の a 要素を組み立てて送るのではなく、URL そのものを送信したい場合は以下のようにします。

await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
    "Please confirm your account by: " + 
    HtmlEncoder.Default.Encode(callbackUrl).Replace("&amp;", "&"));

その際、その際クエリ文字列内の &amp; は & にしないと ConfirmEmail の OnGetAsync(string userId, string code) の code が null になってしまうので注意してください。

上記の結果、送信されたメールは以下の画像のようになります。(メーラーは Windows Live メールです)

送信されたメール

(8) RegisterConfirmation ページの修正

Areas/Identity/Pages/Account フォルダの RegisterConfirmation.cshtml.cs を開いて OnGetAsync メソッドを以下のように修正します。

RegisterConfirmation ページの修正

Email Confirmation を実装したので ConfirmationEmail ページへのリンクを表示する必要はありません。そのリンクを生成するコードをコメントアウトします。

加えて DisplayConfirmAccountLink を falses に設定し、ConfirmationEmail ページへのリンクではなくて単に "Please check your email to confirm your account." と表示されるようにします。

(9) Password Recovery

Password Recovery はユーザーがパスワードを忘れてしまっても復活できる手段を提供するものです。

上のステップ「(6) EmailSender クラスの実装」 まででメールは送信されるようになっていますので、基本的な機能だけなら既存のコードに手を加える必要はありません。

なお、スキャフォールディングで実装されるコードはメール送信機能を実装しないと Password Recovery が使えないコーディングになっていますので注意してください。(メール送信機能なしで Password Recovery を利用するにはコードを書き換える必要があります)

以下に Password Recovery がどのような動きになるかを説明しようと思いましたが、長くなりすぎるので別の記事「Password Recovery (CORE 版)」に書きました。興味があればそちらを見てください。

Tags: , , ,

CORE

About this blog

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

Calendar

<<  July 2020  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar