次の方法で共有


ASP.NET ID を使用したアカウントの確認とパスワードの回復 (C#)

このチュートリアルを実行する前に、まず ログイン、電子メールの確認、パスワードリセットを使用して、セキュリティで保護された ASP.NET MVC 5 Web アプリを作成する必要があります。 このチュートリアルでは、詳細について説明し、ローカル アカウントの確認用に電子メールを設定し、ユーザーが忘れたパスワードを ASP.NET ID にリセットできるようにする方法について説明します。

ローカル ユーザー アカウントでは、ユーザーがアカウントのパスワードを作成する必要があり、そのパスワードは Web アプリに (安全に) 保存されます。 ASP.NET ID はソーシャル アカウントもサポートしています。ソーシャル アカウントでは、ユーザーがアプリのパスワードを作成する必要はありません。 ソーシャル アカウント、第三者 (Google、Twitter、Facebook、Microsoft など) を使用してユーザーを認証します。 このトピックでは、次の内容について説明します。

新しいユーザーは、ローカル アカウントを作成するメール エイリアスを登録します。

アカウント登録ウィンドウの の画像

[登録] ボタンを選択すると、検証トークンを含む確認メールがメール アドレスに送信されます。

電子メール送信確認 を示す画像

ユーザーは、アカウントの確認トークンを含む電子メールを送信されます。

確認トークンの の画像

リンクを選択すると、アカウントが確認されます。

メール アドレスを確認する画像

パスワードの回復/リセット

パスワードを忘れたローカル ユーザーは、メール アカウントにセキュリティ トークンを送信して、パスワードをリセットできます。

パスワードリセットウィンドウを忘れた場合の画像

ユーザーは間もなく、パスワードのリセットを許可するリンクが記載されたメールを受け取ります。

パスワードのリセット電子メール を示す画像
リンクを選択すると、[リセット] ページに移動します。

ユーザー パスワード リセット ウィンドウの を示す画像

リセット ボタンを選択すると、パスワードがリセットされたことを確認します。

パスワード リセットの確認 を示す画像

ASP.NET Web アプリを作成する

まず、Visual Studio 2017インストールして実行します。

  1. 新しい ASP.NET Web プロジェクトを作成し、MVC テンプレートを選択します。 Web フォームでは ASP.NET ID もサポートされているため、Web フォーム アプリでも同様の手順を実行できます。

  2. 認証を個々のユーザー アカウントに変更します。

  3. アプリを実行し、「レジスター」リンクを選択して、ユーザーを登録します。 この時点で、電子メールの検証は [EmailAddress] 属性のみです。

  4. サーバー エクスプローラーで、データ接続\DefaultConnection\Tables\AspNetUsersに移動し、右クリックして [テーブル定義開く] を選択します。

    次の図は、AspNetUsers スキーマを示しています。

    A s p Net Users スキーマを示す画像

  5. AspNetUsers テーブルを右クリックし、[テーブル データの表示]選択します。

    テーブル データの を示す画像

    この時点で、電子メールは確認されていません。

ASP.NET ID の既定のデータ ストアは Entity Framework ですが、他のデータ ストアを使用してフィールドを追加するように構成できます。 このチュートリアルの最後 「その他のリソース」セクションを参照してください。

OWIN スタートアップ クラス (Startup.cs) は、アプリの起動時に呼び出され、App_Start\Startup.Auth.csConfigureAuth メソッドを呼び出します。このメソッドは、OWIN パイプラインを構成し、ASP.NET ID を初期化します。 ConfigureAuth メソッドを調べます。 各 CreatePerOwinContext 呼び出しでは、指定した型のインスタンスを作成するために要求ごとに 1 回呼び出されるコールバック (OwinContextに保存) が登録されます。 各型(ApplicationDbContext, ApplicationUserManager)のコンストラクターおよびCreateメソッドにブレークポイントを設定し、各リクエストでそれらが呼び出されることを確認できます。 ApplicationDbContextApplicationUserManager のインスタンスは OWIN コンテキストに格納され、アプリケーション全体でアクセスできます。 ASP.NET Id は、Cookie ミドルウェアを介して OWIN パイプラインにフックされます。 詳細については、ASP.NET Identity の UserManager クラスのリクエストごとのライフタイム管理について、「」および「」を参照してください。

セキュリティ プロファイルを変更すると、新しいセキュリティ スタンプが生成され、AspNetUsers テーブルの SecurityStamp フィールドに格納されます。 SecurityStamp フィールドはセキュリティ Cookie とは異なっています。 セキュリティ Cookie は、AspNetUsers テーブル (または Identity DB 内の他の場所) には格納されません。 セキュリティ Cookie トークンは、DPAPI 使用して自己署名され、 と有効期限の情報を使用して作成されます。

Cookie ミドルウェアは、各要求で Cookie をチェックします。 Startup クラスの SecurityStampValidator メソッドは DB にヒットし、validateIntervalで指定されているようにセキュリティ スタンプを定期的にチェックします。 これは、セキュリティ プロファイルを変更しない限り、(サンプルでは) 30 分ごとに行われます。 データベースへの乗車を最小限に抑えるために、30 分間隔が選択されました。 詳細については、2 要素認証のチュートリアル を参照してください。

コード内のコメントに従って、UseCookieAuthentication メソッドは Cookie 認証をサポートします。 SecurityStamp フィールドと関連するコードは、アプリにセキュリティの追加レイヤーを提供します。パスワードを変更すると、ログインしたブラウザーからログアウトされます。 SecurityStampValidator.OnValidateIdentity メソッドを使用すると、ユーザーがログインするときに、アプリでセキュリティ トークンを検証できます。これは、パスワードを変更するとき、または外部ログインを使用するときに使用されます。 これは、古いパスワードで生成されたすべてのトークン (Cookie) が無効になるようにするために必要です。 サンプル プロジェクトでは、ユーザーのパスワードを変更すると、ユーザーに対して新しいトークンが生成され、以前のトークンはすべて無効になり、SecurityStamp フィールドが更新されます。

ID システムを使用すると、ユーザーのセキュリティ プロファイルが変更されたときに (たとえば、ユーザーがパスワードを変更したり、関連するログイン (Facebook、Google、Microsoft アカウントなど) を変更したりしたときに)、ユーザーがすべてのブラウザー インスタンスからログアウトされるようにアプリを構成できます。 たとえば、次の図は、シングル サインアウト サンプル アプリを示しています。これにより、ユーザーはすべてのブラウザー インスタンス (この場合は IE、Firefox、Chrome) から 1 つのボタンを選択してサインアウトできます。 または、このサンプルでは、特定のブラウザー インスタンスからのみログアウトできます。

シングル サインアウト サンプル アプリ ウィンドウを示す画像

シングル サインアウト サンプル アプリでは、ASP.NET ID を使用してセキュリティ トークンを再生成する方法を示します。 これは、古いパスワードで生成されたすべてのトークン (Cookie) が無効になるようにするために必要です。 この機能は、アプリケーションにセキュリティの追加レイヤーを提供します。パスワードを変更すると、このアプリケーションにログインした場所にログアウトします。

App_Start\IdentityConfig.cs ファイルには、ApplicationUserManagerEmailService、および SmsService クラスが含まれています。 EmailService クラスと SmsService クラスはそれぞれ IIdentityMessageService インターフェイスを実装するため、各クラスには電子メールと SMS を構成するための一般的なメソッドがあります。 このチュートリアルでは、SendGridを使用して電子メール通知 追加する方法のみを示しますが、SMTP やその他のメカニズムを使用して電子メールを送信できます。

クラスには、ソーシャル ログイン (Facebook、Twitter など) を追加するためのボイラー プレートも含まれています。詳細については、Facebook、Twitter、LinkedIn、Google OAuth2 のサインオン を使用した MVC 5 アプリ チュートリアルを参照してください。

ユーザー ID 情報を含む ApplicationUserManager クラスを調べ、次の機能を構成します。

  • パスワード強度の要件。
  • ユーザーアカウントのロックアウト (試行回数と時間)。
  • 2 要素認証 (2FA)。 私は別のチュートリアルで2FAとSMSをカバーします。
  • 電子メールと SMS サービスを接続する。 (私は別のチュートリアルでSMSをカバーします)。

ApplicationUserManager クラスは、ジェネリック UserManager<ApplicationUser> クラスから派生します。 は、IdentityUserから派生します。 IdentityUser は、ジェネリック IdentityUser クラスから派生します。

//     Default EntityFramework IUser implementation
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
   where TLogin : IdentityUserLogin<TKey>
   where TRole : IdentityUserRole<TKey>
   where TClaim : IdentityUserClaim<TKey>
{
   public IdentityUser()
   {
      Claims = new List<TClaim>();
      Roles = new List<TRole>();
      Logins = new List<TLogin>();
   }

   ///     User ID (Primary Key)
   public virtual TKey Id { get; set; }

   public virtual string Email { get; set; }
   public virtual bool EmailConfirmed { get; set; }

   public virtual string PasswordHash { get; set; }

   ///     A random value that should change whenever a users credentials have changed (password changed, login removed)
   public virtual string SecurityStamp { get; set; }

   public virtual string PhoneNumber { get; set; }
   public virtual bool PhoneNumberConfirmed { get; set; }

   public virtual bool TwoFactorEnabled { get; set; }

   ///     DateTime in UTC when lockout ends, any time in the past is considered not locked out.
   public virtual DateTime? LockoutEndDateUtc { get; set; }

   public virtual bool LockoutEnabled { get; set; }

   ///     Used to record failures for the purposes of lockout
   public virtual int AccessFailedCount { get; set; }
   
   ///     Navigation property for user roles
   public virtual ICollection<TRole> Roles { get; private set; }

   ///     Navigation property for user claims
   public virtual ICollection<TClaim> Claims { get; private set; }

   ///     Navigation property for user logins
   public virtual ICollection<TLogin> Logins { get; private set; }
   
   public virtual string UserName { get; set; }
}

上記のプロパティは、上記の AspNetUsers テーブルのプロパティと一致します。

IUser のジェネリック引数を使用すると、主キーのさまざまな型を使用してクラスを派生できます。 主キーを文字列から int または GUID に変更する方法を示す ChangePK サンプルを参照してください。

ApplicationUser

ApplicationUser (public class ApplicationUserManager : UserManager<ApplicationUser>) は、Models\IdentityModels.cs で次のように定義されます。

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in 
       //   CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, 
    DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

上記の強調表示されたコードでは、ClaimsIdentityが生成されます。 ASP.NET ID と OWIN Cookie 認証はクレーム ベースであるため、フレームワークではアプリがユーザーの ClaimsIdentity を生成する必要があります。 ClaimsIdentity には、ユーザーの名前、年齢、ユーザーが属するロールなど、ユーザーのすべての要求に関する情報があります。 この段階で、ユーザーの要求をさらに追加することもできます。

OWIN AuthenticationManager.SignIn メソッドは、ClaimsIdentity を渡し、ユーザーをサインインさせます。

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties(){
       IsPersistent = isPersistent }, 
       await user.GenerateUserIdentityAsync(UserManager));
}

Facebook、Twitter、LinkedIn、および Google OAuth2 サインオンを使用する MVC 5 アプリでは、ApplicationUser クラスに追加のプロパティを加える方法を示します。

電子メールの確認

新しいユーザーが他のユーザーを偽装していないことを確認するために、新しいユーザーが登録したメールを確認することをお勧めします (つまり、他のユーザーの電子メールに登録されていません)。 ディスカッション フォーラムがあるとします。"bob@example.com""joe@contoso.com"として登録されないようにしたいとします。 電子メールの確認がない場合、"joe@contoso.com" はアプリから不要なメールを受け取る可能性があります。 Bob が誤って "bib@example.com" として登録し、気付かなかったとします。アプリに正しい電子メールがないため、パスワード回復を使用できないとします。 電子メールの確認では、ボットからの保護が制限され、決定されたスパム送信者からの保護は提供されず、登録に使用できる多くの動作する電子メール エイリアスがあります。次のサンプルでは、ユーザーは自分のアカウントが確認されるまでパスワードを変更できません (登録した電子メール アカウントで受信した確認リンクを選択することで)。このワークフローを他のシナリオに適用できます。たとえば、管理者が作成した新しいアカウントのパスワードを確認およびリセットするためのリンクを送信したり、プロファイルを変更したときにユーザーに電子メールを送信したりできます。 一般に、新しいユーザーが電子メール、SMS テキスト メッセージ、または別のメカニズムによって確認される前に、新しいユーザーが Web サイトにデータを投稿できないようにする必要があります。

より完全なサンプルを構築する

このセクションでは、NuGet を使用して、使用するより完全なサンプルをダウンロードします。

  1. 新しい ASP.NET Web プロジェクトを作成します。

  2. パッケージ マネージャー コンソールで、次のコマンドを入力します。

    Install-Package SendGrid
    Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
    

    このチュートリアルでは、SendGrid 使用して電子メールを送信します。 Identity.Samples パッケージは、使用するコードをインストールします。

  3. SSLを使用するように プロジェクトを設定します。

  4. アプリを実行し、[ 登録] リンクを選択し、登録フォームを投稿して、ローカル アカウントの作成をテストします。

  5. 電子メールの確認をシミュレートするデモ電子メール リンクを選択します。

  6. サンプル (アカウント コントローラーの ViewBag.Link コード) からデモ電子メール リンク確認コードを削除します。DisplayEmail および ForgotPasswordConfirmation アクション メソッドと Razor ビューを参照してください。

警告

このサンプルのいずれかのセキュリティ設定を変更した場合、運用アプリは、加えられた変更を明示的に呼び出すセキュリティ監査を受ける必要があります。

App_Start\IdentityConfig.cs のコードを調べる

このサンプルでは、アカウントを作成し、管理者 ロールに追加する方法を示します。 サンプルのメールは、管理者アカウントに使用するメールに置き換える必要があります。 現在、管理者アカウントを作成する最も簡単な方法は、Seed メソッドでプログラムによって行います。 今後、ユーザーとロールを作成および管理できるツールを提供したいと考えています。 サンプル コードでは、ユーザーとロールを作成および管理できますが、ロールとユーザー管理者ページを実行するには、まず管理者アカウントが必要です。 このサンプルでは、DB のシード処理時に管理者アカウントが作成されます。

パスワードを変更し、メール通知を受信できるアカウントに名前を変更します。

警告

セキュリティ - 機密データをソース コードに格納しないでください。

前述のように、スタートアップ クラスの app.CreatePerOwinContext 呼び出しは、アプリ DB コンテンツ、ユーザー マネージャー、ロール マネージャー クラスの Create メソッドにコールバックを追加します。 OWIN パイプラインは、要求ごとにこれらのクラスに対して Create メソッドを呼び出し、各クラスのコンテキストを格納します。 アカウント コントローラーは、HTTP コンテキスト (OWIN コンテキストを含む) からユーザー マネージャーを公開します。

public ApplicationUserManager UserManager
{
    get
    {
        return _userManager ?? 
    HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    }
    private set
    {
        _userManager = value;
    }
}

ユーザーがローカル アカウントを登録すると、HTTP Post Register メソッドが呼び出されます。

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
            var callbackUrl = Url.Action(
               "ConfirmEmail", "Account", 
               new { userId = user.Id, code = code }, 
               protocol: Request.Url.Scheme);

            await UserManager.SendEmailAsync(user.Id, 
               "Confirm your account", 
               "Please confirm your account by clicking this link: <a href=\"" 
                                               + callbackUrl + "\">link</a>");
            // ViewBag.Link = callbackUrl;   // Used only for initial demo.
            return View("DisplayEmail");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

上記のコードでは、モデル データを使用して、入力された電子メールとパスワードを使用して新しいユーザー アカウントを作成します。 電子メール エイリアスがデータ ストア内にある場合、アカウントの作成は失敗し、フォームが再び表示されます。 GenerateEmailConfirmationTokenAsync メソッドは、セキュリティで保護された確認トークンを作成し、ASP.NET ID データ ストアに格納します。 Url.Action メソッドは、UserId と確認トークンを含むリンクを作成します。 このリンクはユーザーに電子メールで送信され、ユーザーはメール アプリのリンクを選択してアカウントを確認できます。

電子メールの確認を設定する

SendGrid のサインアップ ページに移動し、無料アカウントに登録します。 SendGrid を構成するには、次のようなコードを追加します。

public class EmailService : IIdentityMessageService
{
   public Task SendAsync(IdentityMessage message)
   {
      return configSendGridasync(message);
   }

   private Task configSendGridasync(IdentityMessage message)
   {
      var myMessage = new SendGridMessage();
      myMessage.AddTo(message.Destination);
      myMessage.From = new System.Net.Mail.MailAddress(
                          "Joe@contoso.com", "Joe S.");
      myMessage.Subject = message.Subject;
      myMessage.Text = message.Body;
      myMessage.Html = message.Body;

      var credentials = new NetworkCredential(
                 ConfigurationManager.AppSettings["mailAccount"],
                 ConfigurationManager.AppSettings["mailPassword"]
                 );

      // Create a Web transport for sending email.
      var transportWeb = new Web(credentials);

      // Send the email.
      if (transportWeb != null)
      {
         return transportWeb.DeliverAsync(myMessage);
      }
      else
      {
         return Task.FromResult(0);
      }
   }
}

手記

電子メール クライアントは、テキスト メッセージのみを受け入れる場合が多い (HTML は使用できません)。 メッセージはテキストと HTML で指定する必要があります。 上記の SendGrid サンプルでは、上記の myMessage.TextmyMessage.Html コードを使用してこれを行います。

次のコードは、MailMessage クラスを使用して電子メールを送信 message.Body、リンクのみを返す方法を示しています。

void sendMail(Message message)
{
#region formatter
   string text = string.Format("Please click on this link to {0}: {1}", message.Subject, message.Body);
   string html = "Please confirm your account by clicking this link: <a href=\"" + message.Body + "\">link</a><br/>";

   html += HttpUtility.HtmlEncode(@"Or click on the copy the following link on the browser:" + message.Body);
#endregion

   MailMessage msg = new MailMessage();
   msg.From = new MailAddress("joe@contoso.com");
   msg.To.Add(new MailAddress(message.Destination));
   msg.Subject = message.Subject;
   msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
   msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));

   SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", Convert.ToInt32(587));
   System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("joe@contoso.com", "XXXXXX");
   smtpClient.Credentials = credentials;
   smtpClient.EnableSsl = true;
   smtpClient.Send(msg);
}

警告

セキュリティ - 機密データをソース コードに格納しないでください。 アカウントと資格情報は appSetting に格納されます。 Azure では、これらの値を Azure portal の [ の構成] タブに安全に格納できます。 ASP.NET と Azureにパスワードやその他の機密データをデプロイするためのベスト プラクティス 参照してください。

SendGrid の資格情報を入力し、アプリを実行し、電子メール エイリアスに登録すると、メールの確認リンクを選択できます。 Outlook.com の電子メールアカウントでこれを行う方法については、John Attenの「C# SMTP Configuration for Outlook.Com SMTP Host」と「ASP.NET Identity 2.0: Setting Up Account Validation and Two-Factor Authorization」に関する投稿を参照してください。

ユーザーが [登録] ボタンを選択すると、検証トークンを含む確認メールがメール アドレスに送信されます。

メール送信確認ウィンドウの画像

ユーザーは、アカウントの確認トークンを含む電子メールを送信されます。

受信したメールの画像

コードを調べる

次のコードは、POST ForgotPassword メソッドを示しています。

public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindByNameAsync(model.Email);
        if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
        {
            // Don't reveal that the user does not exist or is not confirmed
            return View("ForgotPasswordConfirmation");
        }

        var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
        var callbackUrl = Url.Action("ResetPassword", "Account", 
    new { UserId = user.Id, code = code }, protocol: Request.Url.Scheme);
        await UserManager.SendEmailAsync(user.Id, "Reset Password", 
    "Please reset your password by clicking here: <a href=\"" + callbackUrl + "\">link</a>");        
        return View("ForgotPasswordConfirmation");
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

ユーザーの電子メールが確認されていない場合、メソッドは自動的に失敗します。 無効なメール アドレスに対してエラーが投稿された場合、悪意のあるユーザーはその情報を使用して、攻撃対象の有効な userId (電子メール エイリアス) を見つける可能性があります。

次のコードは、ユーザーが送信された電子メールで確認リンクを選択したときに呼び出される、アカウント コントローラーの ConfirmEmail メソッドを示しています。

public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
    if (userId == null || code == null)
    {
        return View("Error");
    }
    var result = await UserManager.ConfirmEmailAsync(userId, code);
    if (result.Succeeded)
    {
        return View("ConfirmEmail");
    }
    AddErrors(result);
    return View();
}

忘れたパスワード トークンが使用されると、無効になります。 Create メソッド (App_Start\IdentityConfig.cs ファイル内) の次のコード変更により、トークンの有効期限が 3 時間に設定されます。

if (dataProtectionProvider != null)
 {
    manager.UserTokenProvider =
       new DataProtectorTokenProvider<ApplicationUser>
          (dataProtectionProvider.Create("ASP.NET Identity"))
          {                    
             TokenLifespan = TimeSpan.FromHours(3)
          };
 }

上記のコードでは、忘れたパスワードと電子メール確認トークンは 3 時間で期限切れになります。 既定の TokenLifespan は 1 日です。

次のコードは、電子メールの確認方法を示しています。

// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
   if (userId == null || code == null)
   {
      return View("Error");
   }
   IdentityResult result;
   try
   {
      result = await UserManager.ConfirmEmailAsync(userId, code);
   }
   catch (InvalidOperationException ioe)
   {
      // ConfirmEmailAsync throws when the userId is not found.
      ViewBag.errorMessage = ioe.Message;
      return View("Error");
   }

   if (result.Succeeded)
   {
      return View();
   }

   // If we got this far, something failed.
   AddErrors(result);
   ViewBag.errorMessage = "ConfirmEmail failed";
   return View("Error");
}

アプリの安全性を高めるために、ASP.NET Identity では Two-Factor 認証 (2FA) がサポートされています。 「ASP.NET ID 2.0: John Atten によるアカウント検証と Two-Factor 承認 の設定」を参照してください。 ログインパスワードの試行が失敗した場合に対してアカウントのロックアウトを設定することもできますが、その方法ではログインが DOS ロックアウトの影響を受けやすくなります。 アカウント ロックアウトは 2FA でのみ使用することをお勧めします。

その他のリソース