ASP.NET Identity에서 SMS 및 전자 메일을 사용한 2단계 인증

작성자: Hao Kung, Pranav Rastogi, Rick Anderson, Suhas Joshi

이 자습서에서는 SMS 및 이메일을 사용하여 2FA(2단계 인증)를 설정하는 방법을 보여 줍니다.

이 문서는 릭 앤더슨 (@RickAndMSFT), 프라나브 라스토기 (@rustd), 하오 쿵, 수하스 조시에 의해 작성되었습니다. NuGet 샘플은 주로 Hao Kung에 의해 작성되었습니다.

이 항목에서는 다음에 대해 설명합니다.

ID 샘플 빌드

이 섹션에서는 NuGet을 사용하여 작업할 샘플을 다운로드합니다. 먼저 웹 또는Visual Studio 2013 Visual Studio Express 2013을 설치하고 실행합니다. Visual Studio 2013 업데이트 2 이상을 설치합니다.

참고

경고: 이 자습서를 완료하려면 Visual Studio 2013 업데이트 2 를 설치해야 합니다.

  1. ASP.NET 웹 프로젝트를 새로 만듭니다.

  2. 패키지 관리자 콘솔에서 다음 명령을 입력합니다.

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

    이 자습서에서는 SendGrid 를 사용하여 Sms 문자 메시지로 전자 메일 및 Twilio 또는 ASPSMS 를 보냅니다. 패키지는 Identity.Samples 작업할 코드를 설치합니다.

  3. SSL을 사용하도록 프로젝트를 설정합니다.

  4. 선택 사항: Email 확인 자습서의 지침에 따라 SendGrid를 연결한 다음, 앱을 실행하고 이메일 계정을 등록합니다.

  5. 선택적: 샘플에서 데모 전자 메일 링크 확인 코드를 제거합니다( ViewBag.Link 계정 컨트롤러의 코드). DisplayEmailForgotPasswordConfirmation 작업 메서드 및 razor 뷰를 참조하세요.

  6. 선택적:ViewBag.Status 관리 및 계정 컨트롤러 및 Views\Account\VerifyCode.cshtmlViews\Manage\VerifyPhoneNumber.cshtml razor 뷰에서 코드를 제거합니다. 또는 전자 메일 및 SMS 메시지를 연결하고 보낼 필요 없이 이 앱이 로컬에서 작동하는 방식을 테스트하기 위해 디스플레이를 유지할 ViewBag.Status 수 있습니다.

참고

경고: 이 샘플에서 보안 설정을 변경하는 경우 프로덕션 앱은 변경 내용을 명시적으로 호출하는 보안 감사를 받아야 합니다.

2단계 인증을 위한 SMS 설정

이 자습서에서는 Twilio 또는 ASPSMS를 사용하기 위한 지침을 제공하지만 다른 SMS 공급자를 사용할 수 있습니다.

  1. SMS 공급자를 사용하여 사용자 계정 만들기

    Twilio 또는 ASPSMS 계정을 만듭니다.

  2. 추가 패키지 설치 또는 서비스 참조 추가

    Twilio:
    패키지 관리자 콘솔에서 다음 명령을 입력합니다.
    Install-Package Twilio

    ASPSMS:
    다음 서비스 참조를 추가해야 합니다.

    서비스 참조 창 추가 이미지

    주소:
    https://webservice.aspsms.com/aspsmsx2.asmx?WSDL

    네임스페이스:
    ASPSMSX2

  3. SMS 공급자 사용자 자격 증명 구성

    Twilio:
    Twilio 계정의 대시보드 탭에서 계정 SID인증 토큰을 복사합니다.

    ASPSMS:
    계정 설정에서 Userkey 로 이동하여 자체 정의 암호와 함께 복사 합니다.

    나중에 이러한 값을 및 변수에 저장합니다 SMSAccountIdentificationSMSAccountPassword .

  4. SenderID/Originator 지정

    Twilio:
    숫자 탭에서 Twilio 전화 번호를 복사합니다.

    ASPSMS:
    보낸 사람 잠금 해제 메뉴에서 하나 이상의 보낸 사람의 잠금을 해제하거나 영숫자 보낸 사람(모든 네트워크에서 지원되지 않음)을 선택합니다.

    나중에 변수에 이 값을 저장합니다 SMSAccountFrom .

  5. SMS 공급자 자격 증명을 앱으로 전송

    앱에서 자격 증명 및 보낸 사람 전화 번호를 사용할 수 있도록 합니다.

    public static class Keys
    {
       public static string SMSAccountIdentification = "My Idenfitication";
       public static string SMSAccountPassword = "My Password";
       public static string SMSAccountFrom = "+15555551234";
    }
    

    경고

    보안 - 소스 코드에 중요한 데이터를 저장하지 않습니다. 위의 코드에 계정 및 자격 증명이 추가되어 샘플을 단순하게 유지합니다. Jon Atten의 ASP.NET MVC: 프라이빗 설정을 소스 제어에서 벗어나지 않도록 유지를 참조하세요.

  6. SMS 공급자에 대한 데이터 전송 구현

    SmsServiceApp_Start\IdentityConfig.cs 파일에서 클래스를 구성합니다.

    사용된 SMS 공급자에 따라 Twilio 또는 ASPSMS 섹션을 활성화합니다.

    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Twilio Begin
            // var Twilio = new TwilioRestClient(
            //   Keys.SMSAccountIdentification,
            //   Keys.SMSAccountPassword);
            // var result = Twilio.SendMessage(
            //   Keys.SMSAccountFrom,
            //   message.Destination, message.Body
            // );
            // Status is one of Queued, Sending, Sent, Failed or null if the number is not valid
            // Trace.TraceInformation(result.Status);
            // Twilio doesn't currently have an async API, so return success.
            // return Task.FromResult(0);
            // Twilio End
    
            // ASPSMS Begin 
            // var soapSms = new WebApplication1.ASPSMSX2.ASPSMSX2SoapClient("ASPSMSX2Soap");
            // soapSms.SendSimpleTextSMS(
            //   Keys.SMSAccountIdentification,
            //   Keys.SMSAccountPassword,
            //   message.Destination,
            //   Keys.SMSAccountFrom,
            //   message.Body);
            // soapSms.Close();
            // return Task.FromResult(0);
            // ASPSMS End
        }
    }
    
  7. 앱을 실행하고 이전에 등록한 계정으로 로그인합니다.

  8. 컨트롤러에서 작업 메서드를 활성화하는 사용자 ID를 IndexManage 클릭합니다.

    앱에 로그인한 등록된 계정 이미지

  9. 추가를 클릭합니다.

    전화 번호 추가 링크 이미지

  10. 몇 초 후에 확인 코드가 포함된 문자 메시지가 표시됩니다. 입력하고 제출을 누릅니다.

    전화 확인 코드 항목을 보여 주는 이미지

  11. 관리 보기에는 전화 번호가 추가된 것으로 표시됩니다.

    전화 번호를 보여 주는 보기 관리 창 이미지

코드 검사

// GET: /Account/Index
public async Task<ActionResult> Index(ManageMessageId? message)
{
    ViewBag.StatusMessage =
        message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
        : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
        : message == ManageMessageId.SetTwoFactorSuccess ? "Your two factor provider has been set."
        : message == ManageMessageId.Error ? "An error has occurred."
        : message == ManageMessageId.AddPhoneSuccess ? "The phone number was added."
        : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
        : "";

    var model = new IndexViewModel
    {
        HasPassword = HasPassword(),
        PhoneNumber = await UserManager.GetPhoneNumberAsync(User.Identity.GetUserId()),
        TwoFactor = await UserManager.GetTwoFactorEnabledAsync(User.Identity.GetUserId()),
        Logins = await UserManager.GetLoginsAsync(User.Identity.GetUserId()),
        BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(User.Identity.GetUserId())
    };
    return View(model);
}

컨트롤러의 Manage 작업 메서드는 Index 이전 작업을 기반으로 상태 메시지를 설정하고 로컬 암호를 변경하거나 로컬 계정을 추가하는 링크를 제공합니다. 또한 메서드는 Index 상태 또는 2FA 전화 번호, 외부 로그인, 2FA 사용 및 이 브라우저에 대한 2FA 메서드를 표시합니다(나중에 설명). 제목 표시줄에서 사용자 ID(전자 메일)를 클릭하면 메시지가 전달되지 않습니다. 전화 번호 : 제거 링크를 클릭하면 쿼리 문자열로 전달됩니다Message=RemovePhoneSuccess.

https://localhost:44300/Manage?Message=RemovePhoneSuccess

[제거된 전화 번호 이미지]

작업 메서드는 AddPhoneNumber SMS 메시지를 받을 수 있는 전화 번호를 입력하는 대화 상자를 표시합니다.

// GET: /Account/AddPhoneNumber
public ActionResult AddPhoneNumber()
{
   return View();
}

전화 번호 작업 추가 대화 상자 이미지

확인 코드 보내기 단추를 클릭하면 전화 번호가 HTTP POST AddPhoneNumber 작업 메서드에 게시됩니다.

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

    // Generate the token 
    var code = await UserManager.GenerateChangePhoneNumberTokenAsync(
                               User.Identity.GetUserId(), model.Number);
    if (UserManager.SmsService != null)
    {
        var message = new IdentityMessage
        {
            Destination = model.Number,
            Body = "Your security code is: " + code
        };
        // Send token
        await UserManager.SmsService.SendAsync(message);
    }
    return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}

메서드는 GenerateChangePhoneNumberTokenAsync SMS 메시지에 설정될 보안 토큰을 생성합니다. SMS 서비스가 구성된 경우 토큰은 "보안 코드는 토큰>"이라는 문자열로 전송됩니다<. SmsService.SendAsync 에 대한 메서드가 비동기적으로 호출된 다음 앱이 확인 코드를 입력할 VerifyPhoneNumber 수 있는 작업 메서드(다음 대화 상자를 표시함)로 리디렉션됩니다.

전화 번호 확인 작업 방법 대화 상자 이미지

코드를 입력하고 제출을 클릭하면 코드가 HTTP POST VerifyPhoneNumber 작업 메서드에 게시됩니다.

// POST: /Account/VerifyPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);
    if (result.Succeeded)
    {
        var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
        if (user != null)
        {
            await SignInAsync(user, isPersistent: false);
        }
        return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
    }
    // If we got this far, something failed, redisplay form
    ModelState.AddModelError("", "Failed to verify phone");
    return View(model);
}

메서드는 ChangePhoneNumberAsync 게시된 보안 코드를 확인합니다. 코드가 올바르면 전화 번호가 테이블의 AspNetUsers 필드에 추가 PhoneNumber 됩니다. 해당 호출이 성공하면 메서드가 SignInAsync 호출됩니다.

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
   // Clear the temporary cookies used for external and two factor sign ins
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, 
       DefaultAuthenticationTypes.TwoFactorCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties
    {
       IsPersistent = isPersistent 
    }, 
       await user.GenerateUserIdentityAsync(UserManager));
}

매개 변수는 isPersistent 인증 세션이 여러 요청에 걸쳐 유지되는지 여부를 설정합니다.

보안 프로필을 변경하면 새 보안 스탬프가 생성되고 AspNetUsers 테이블의 필드에 저장 SecurityStamp 됩니다. SecurityStamp 필드는 보안 쿠키와 다릅니다. 보안 쿠키는 테이블(또는 ID DB의 다른 위치)에 저장 AspNetUsers 되지 않습니다. 보안 쿠키 토큰은 DPAPI 를 사용하여 자체 서명되며 및 만료 시간 정보를 사용하여 UserId, SecurityStamp 만들어집니다.

쿠키 미들웨어는 각 요청에서 쿠키를 확인합니다. 클래스의 Startup 메서드는 SecurityStampValidator DB에 도달하고 에 지정된 validateInterval대로 보안 스탬프를 주기적으로 확인합니다. 이는 보안 프로필을 변경하지 않는 한 30분마다(샘플에서) 발생합니다. 데이터베이스로의 여행을 최소화하기 위해 30분 간격이 선택되었습니다.

SignInAsync 보안 프로필이 변경되면 메서드를 호출해야 합니다. 보안 프로필이 변경되면 데이터베이스가 필드를 업데이트 SecurityStamp 하고 메서드를 호출 SignInAsync 하지 않으면 다음에 OWIN 파이프라인이 데이터베이스(validateInterval)에 도달할 때까지 로그인 상태를 유지합니다. 즉시 반환하도록 메서드를 SignInAsync 변경하고 쿠키 validateInterval 속성을 30분에서 5초로 설정하여 이를 테스트할 수 있습니다.

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
   return;

   // Clear any partial cookies from external or two factor partial sign ins
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, 
       DefaultAuthenticationTypes.TwoFactorCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties
    {
       IsPersistent = isPersistent 
    }, 
       await user.GenerateUserIdentityAsync(UserManager));
}
public void ConfigureAuth(IAppBuilder app) {
    // Configure the db context, user manager and role manager to use a single instance per request
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

    // Enable the application to use a cookie to store information for the signed in user
    // and to use a cookie to temporarily store information about a user logging in with a 
    // third party login provider
    // Configure the sign in cookie
    app.UseCookieAuthentication(new CookieAuthenticationOptions {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider {
            // Enables the application to validate the security stamp when the user logs in.
            // This is a security feature which is used when you change a password or add 
            // an external login to your account.  
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                //validateInterval: TimeSpan.FromMinutes(30),
                validateInterval: TimeSpan.FromSeconds(5),
                regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
        }
    });
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

위의 코드가 변경되면 보안 프로필을 변경할 수 있습니다(예: Two Factor Enabled 상태 변경). 메서드가 실패하면 5초 SecurityStampValidator.OnValidateIdentity 후에 로그아웃됩니다. 메서드에서 SignInAsync 반환 줄을 제거하고 다른 보안 프로필을 변경하면 로그아웃되지 않습니다. 메서드는 SignInAsync 새 보안 쿠키를 생성합니다.

2단계 인증 사용

샘플 앱에서는 UI를 사용하여 2FA(2단계 인증)를 사용하도록 설정해야 합니다. 2FA를 사용하도록 설정하려면 탐색 모음에서 사용자 ID(이메일 별칭)를 클릭합니다. 2단계 인증을 사용하도록 설정하는 U I 이미지
2FA 사용을 클릭합니다.2단계 인증 사용 링크를 보여 주는 사용자 ID를 클릭한 후 이미지 로그아웃한 다음 다시 로그인합니다. 전자 메일을 사용하도록 설정한 경우( 이전 자습서 참조) 2FA에 대한 SMS 또는 이메일을 선택할 수 있습니다.확인 보내기 옵션을 표시하는 이미지 코드 확인 페이지는 SMS 또는 전자 메일에서 코드를 입력할 수 있는 위치에 표시됩니다.코드 확인 페이지의 이미지이 브라우저 검사 기억 상자를 클릭하면 2FA를 사용하여 해당 컴퓨터 및 브라우저에 로그온할 필요가 없습니다. 2FA를 사용하도록 설정하고 이 브라우저 기억 을 클릭하면 컴퓨터에 액세스할 수 없는 한 계정에 액세스하려는 악의적인 사용자로부터 강력한 2FA 보호를 제공합니다. 정기적으로 사용하는 모든 개인 컴퓨터에서 이 작업을 수행할 수 있습니다. 이 브라우저 기억 을 설정하면 정기적으로 사용하지 않는 컴퓨터에서 2FA의 보안이 추가되고 사용자 컴퓨터에서 2FA를 통과하지 않아도 편리합니다.

2단계 인증 공급자를 등록하는 방법

새 MVC 프로젝트를 만들 때 IdentityConfig.cs 파일에는 2단계 인증 공급자를 등록하는 다음 코드가 포함되어 있습니다.

public static ApplicationUserManager Create(
   IdentityFactoryOptions<ApplicationUserManager> options, 
   IOwinContext context) 
{
    var manager = new ApplicationUserManager(
       new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
    // Configure validation logic for usernames
    manager.UserValidator = new UserValidator<ApplicationUser>(manager)
    {
        AllowOnlyAlphanumericUserNames = false,
        RequireUniqueEmail = true
    };
    // Configure validation logic for passwords
    manager.PasswordValidator = new PasswordValidator
    {
        RequiredLength = 6,
        RequireNonLetterOrDigit = true,
        RequireDigit = true,
        RequireLowercase = true,
        RequireUppercase = true,
    };
    // Register two factor authentication providers. This application uses Phone and Emails as a 
    // step of receiving a code for verifying the user
    // You can write your own provider and plug it in here.
    manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
    {
        MessageFormat = "Your security code is: {0}"
    });
    manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
    {
        Subject = "Security Code",
        BodyFormat = "Your security code is: {0}"
    });
    manager.EmailService = new EmailService();
    manager.SmsService = new SmsService();

    var dataProtectionProvider = options.DataProtectionProvider;
    if (dataProtectionProvider != null)
    {
        manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>
           (dataProtectionProvider.Create("ASP.NET Identity"));
    }
    return manager;
}

2FA에 대한 전화 번호 추가

컨트롤러의 Manage 작업 메서드는 AddPhoneNumber 보안 토큰을 생성하고 제공한 전화 번호로 보냅니다.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    // Generate the token and send it
    var code = await UserManager.GenerateChangePhoneNumberTokenAsync(
       User.Identity.GetUserId(), model.Number);
    if (UserManager.SmsService != null)
    {
        var message = new IdentityMessage
        {
            Destination = model.Number,
            Body = "Your security code is: " + code
        };
        await UserManager.SmsService.SendAsync(message);
    }
    return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}

토큰을 보낸 후 2FA에 SMS를 등록하는 VerifyPhoneNumber 코드를 입력할 수 있는 작업 메서드로 리디렉션됩니다. SMS 2FA는 전화 번호를 확인할 때까지 사용되지 않습니다.

2FA 사용

작업 메서드를 EnableTFA 사용하면 2FA를 사용할 수 있습니다.

// POST: /Manage/EnableTFA
[HttpPost]
public async Task<ActionResult> EnableTFA()
{
    await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true);
    var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
    if (user != null)
    {
        await SignInAsync(user, isPersistent: false);
    }
    return RedirectToAction("Index", "Manage");
}

2FA 사용은 SignInAsync 보안 프로필에 대한 변경이므로 를 호출해야 합니다. 2FA를 사용하도록 설정하면 사용자가 등록한 2FA 접근 방식(샘플의 SMS 및 이메일)을 사용하여 2FA를 사용하여 로그인해야 합니다.

QR 코드 생성기와 같은 2FA 공급자를 더 추가하거나 직접 작성할 수 있습니다.

참고

2FA 코드는 시간 기반 일회용 암호 알고리즘 을 사용하여 생성되며 코드는 6분 동안 유효합니다. 코드를 입력하는 데 6분 이상 걸리는 경우 잘못된 코드 오류 메시지가 표시됩니다.

소셜 및 로컬 로그인 계정 결합

이메일 링크를 클릭하여 로컬 및 소셜 계정을 결합할 수 있습니다. 다음 시퀀스에서 "RickAndMSFT@gmail.com"는 먼저 로컬 로그인으로 만들어지지만 먼저 계정을 소셜 로그로 만든 다음 로컬 로그인을 추가할 수 있습니다.

전자 메일 링크를 선택하는 이미지

관리 링크를 클릭합니다. 이 계정과 연결된 외부(소셜 로그인)가 0개임을 확인합니다.

다음 페이지를 표시하고 관리를 선택하는 이미지

다른 로그인 서비스에 대한 링크를 클릭하고 앱 요청을 수락합니다. 두 계정이 결합되어 두 계정 중 하나로 로그온할 수 있습니다. 인증 서비스의 소셜 로그가 다운되거나 소셜 계정에 대한 액세스 권한이 손실된 경우 사용자가 로컬 계정을 추가하도록 할 수 있습니다.

다음 이미지에서 Tom은 소셜 로그인입니다( 외부 로그인에서 볼 수 있음: 페이지에 표시된 1).

외부 로그인 및 암호 선택 위치를 보여 주는 이미지

암호 선택을 클릭하면 동일한 계정과 연결된 로컬 로그온을 추가할 수 있습니다.

암호 선택 페이지의 이미지

무차별 암호 대입 공격으로부터 계정 잠금

사용자 잠금을 사용하도록 설정하여 사전 공격으로부터 앱의 계정을 보호할 수 있습니다. 메서드의 다음 코드는 ApplicationUserManager Create 잠금을 구성합니다.

// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;

위의 코드는 2단계 인증에 대해서만 잠금을 사용하도록 설정합니다. 계정 컨트롤러의 메서드에서 Login true로 변경 shouldLockout 하여 로그인 잠금을 사용하도록 설정할 수 있지만 DOS 로그인 공격에 취약한 계정을 만들기 때문에 로그인에 대해 잠금을 사용하도록 설정하지 않는 것이 좋습니다. 샘플 코드에서 메서드에서 만든 관리자 계정에 대해 잠금이 ApplicationDbInitializer Seed 비활성화됩니다.

public static void InitializeIdentityForEF(ApplicationDbContext db)
{
    var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
    var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
    const string name = "admin@example.com";
    const string roleName = "Admin";

    //Create Role Admin if it does not exist
    var role = roleManager.FindByName(roleName);
    if (role == null)
    {
        role = new IdentityRole(roleName);
        var roleresult = roleManager.Create(role);
    }

    var user = userManager.FindByName(name);
    if (user == null)
    {
        user = new ApplicationUser { UserName = name, Email = name };
        var result = userManager.Create(user, GetSecurePassword());
        result = userManager.SetLockoutEnabled(user.Id, false);
    }

    // Add user admin to Role Admin if not already added
    var rolesForUser = userManager.GetRoles(user.Id);
    if (!rolesForUser.Contains(role.Name))
    {
        var result = userManager.AddToRole(user.Id, role.Name);
    }
}

사용자에게 유효성이 검사된 전자 메일 계정을 갖도록 요구

다음 코드에서는 사용자가 로그인하기 전에 유효성이 검사된 전자 메일 계정을 갖도록 요구합니다.

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

   // Require the user to have a confirmed email before they can log on.
    var user = await UserManager.FindByNameAsync(model.Email);
    if (user != null)
    {
       if (!await UserManager.IsEmailConfirmedAsync(user.Id))
       {
          ViewBag.errorMessage = "You must have a confirmed email to log on.";
          return View("Error");
       }         
    }
    // This doen't count login failures towards lockout only two factor authentication
    // To enable password failures to trigger lockout, change to shouldLockout: true
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, 
       model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

SignInManager가 2FA 요구 사항을 확인하는 방법

검사 로컬 로그인 및 소셜 로그를 모두 사용하여 2FA가 사용하도록 설정되어 있는지 확인합니다. 2FA를 사용하도록 설정 SignInManager 하면 로그온 메서드가 를 반환 SignInStatus.RequiresVerification하고 사용자가 작업 메서드로 SendCode 리디렉션됩니다. 여기서 로그를 순서대로 완료하기 위해 코드를 입력해야 합니다. 사용자에게 RememberMe가 사용자 로컬 쿠키에 설정된 경우 가 SignInManager 반환 SignInStatus.Success 되며 2FA를 통과할 필요가 없습니다.

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

   // Require the user to have a confirmed email before they can log on.
    var user = await UserManager.FindByNameAsync(model.Email);
    if (user != null)
    {
       if (!await UserManager.IsEmailConfirmedAsync(user.Id))
       {
          ViewBag.errorMessage = "You must have a confirmed email to log on.";
          return View("Error");
       }         
    }
    // This doen't count login failures towards lockout only two factor authentication
    // To enable password failures to trigger lockout, change to shouldLockout: true
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, 
       model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
    if (loginInfo == null)
    {
        return RedirectToAction("Login");
    }

    // Sign in the user with this external login provider if the user already has a login
    var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            // If the user does not have an account, then prompt the user to create an account
            ViewBag.ReturnUrl = returnUrl;
            ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
            return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
    }
}

다음 코드는 작업 메서드를 SendCode 보여줍니다. SelectListItem은 사용자에 대해 사용하도록 설정된 모든 2FA 메서드를 사용하여 만들어집니다. SelectListItemDropDownListFor 도우미에 전달되므로 사용자가 2FA 접근 방식(일반적으로 전자 메일 및 SMS)을 선택할 수 있습니다.

public async Task<ActionResult> SendCode(string returnUrl)
{
    var userId = await SignInManager.GetVerifiedUserIdAsync();
    if (userId == null)
    {
        return View("Error");
    }
    var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
    var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
    return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl });
}

사용자가 2FA 접근 방식을 HTTP POST SendCode 게시하면 작업 메서드가 호출되고, SignInManager 는 2FA 코드를 보내고, 사용자는 코드를 입력하여 로그인을 완료할 VerifyCode 수 있는 작업 메서드로 리디렉션됩니다.

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

    // Generate the token and send it
    if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider))
    {
        return View("Error");
    }
    return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl });
}

2FA 잠금

로그인 암호 시도 실패 시 계정 잠금을 설정할 수 있지만 이 방법을 사용하면 로그인이 DOS 잠금에 취약해집니다. 2FA에서만 계정 잠금을 사용하는 것이 좋습니다. 가 ApplicationUserManager 만들어지면 샘플 코드는 2FA 잠금을 설정하고 MaxFailedAccessAttemptsBeforeLockout 5로 설정합니다. 사용자가 로컬 계정 또는 소셜 계정을 통해 로그인하면 2FA에서 실패한 각 시도가 저장되고 최대 시도에 도달하면 사용자가 5분 동안 잠깁니다(로 잠금 시간을 DefaultAccountLockoutTimeSpan설정할 수 있음).

추가 리소스