Я использую Web API 2 и хочу защитить свои API с помощью Microsoft ASP.NET Identity 2.2.2; все хорошо, пока я не включу 2fa для пользователей в базе данных.
После того, как я это сделаю, при трассировке кода в классе signInHelper
в методе GetVerifiedUserIdAsync
результат всегда будет нулевым.
Мой Startup
класс:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.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
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
}
Конфигурация удостоверения:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
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 = false,
RequireLowercase = true,
RequireUppercase = true,
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
// 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 in here.
manager.RegisterTwoFactorProvider(
"PhoneCode",
new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Security Code : {0}"
});
manager.RegisterTwoFactorProvider(
"EmailCode",
new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Security Code : {0}"
});
manager.SmsService = new SmsService();
manager.EmailService = new EmailService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your email service here to send an email.
// Credentials:
const string credentialUserName = "";
const string sentFrom = "";
const string pwd = "";
// Configure the client:
var client =
new System.Net.Mail.SmtpClient("smtp-mail.outlook.com")
{
Port = 587,
DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network,
UseDefaultCredentials = false
};
// Creatte the credentials:
var credentials =
new NetworkCredential(credentialUserName, pwd);
client.EnableSsl = true;
client.Credentials = credentials;
// Create the message:
var mail =
new System.Net.Mail.MailMessage(sentFrom, message.Destination)
{
Subject = message.Subject,
Body = message.Body
};
// Send:
return client.SendMailAsync(mail);
}
}
public class SmsService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your SMS service here to send a text message.
return Task.FromResult(0);
}
}
Мой AccountController
:
[Authorize]
[RoutePrefix("api/Account")]
public class AccountController : ApiController
{
private const string LocalLoginProvider = "Local";
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
}
private SignInHelper _helper;
private SignInHelper SignInHelper
{
get
{
if (_helper == null)
{
_helper = new SignInHelper(UserManager, Authentication);
}
return _helper;
}
}
// POST: /Account/Login
[AllowAnonymous]
[Route("Login")]
public async Task<IHttpActionResult> Login([FromBody]Domain.LoginViewModel model)
{
if (model == null)
{
return BadRequest("Model is Invalid");
}
// This doesn't count login failures towards lockout only two factor authentication
// To enable password failures to trigger lockout, change to shouldLockout: true
var result = await SignInHelper.PasswordSignIn(model.UserName, model.Password, true, shouldLockout: true);
switch (result)
{
case SignInHelper.SignInStatus.Success:
{
return Ok("Success");
}
case SignInHelper.SignInStatus.LockedOut:
return BadRequest("Lockout");
case SignInHelper.SignInStatus.RequiresTwoFactorAuthentication:
{
var res = SendCode("PhoneCode");
if (res.Result)
return Ok(User.Identity.GetUserId());
return BadRequest("Error");
}
case SignInHelper.SignInStatus.Failure:
default:
ModelState.AddModelError("", "Login Attempt Failed");
return BadRequest(ModelState);
}
}
private async Task<bool> SendCode(string provider)
{
if (!await SignInHelper.SendTwoFactorCode(provider))
{
return false;
}
return true;
}
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }
protected override void Dispose(bool disposing)
{
if (disposing && _userManager != null)
{
_userManager.Dispose();
_userManager = null;
}
base.Dispose(disposing);
}
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
}
SignInHelper
класс
public class SignInHelper
{
private readonly ApplicationUserManager _appUserManager;
private readonly IAuthenticationManager _authManager;
public SignInHelper(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
{
this._appUserManager = userManager;
this._authManager = authenticationManager;
}
public async Task SignInAsync(ApplicationUser user, bool isPersistent, bool rememberBrowser)
{
// Clear any partial cookies from external or two factor partial sign ins
_authManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie);
var userIdentity = await user.GenerateUserIdentityAsync(_appUserManager, DefaultAuthenticationTypes.TwoFactorCookie);
if (rememberBrowser)
{
var rememberBrowserIdentity = _authManager.CreateTwoFactorRememberBrowserIdentity(user.Id);
_authManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity, rememberBrowserIdentity);
}
else
{
_authManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity);
}
}
public async Task<bool> SendTwoFactorCode(string provider)
{
var userId = await GetVerifiedUserIdAsync();
if (userId == null)
{
return false;
}
var token = await _appUserManager.GenerateTwoFactorTokenAsync(userId, provider);
// See IdentityConfig.cs to plug in Email/SMS services to actually send the code
await _appUserManager.NotifyTwoFactorTokenAsync(userId, provider, token);
return true;
}
public async Task<string> GetVerifiedUserIdAsync()
{
var result = await _authManager.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorCookie);
return !string.IsNullOrEmpty(result?.Identity?.GetUserId()) ? result.Identity.GetUserId() : null;
}
public async Task<bool> HasBeenVerified()
{
return await GetVerifiedUserIdAsync() != null;
}
public enum SignInStatus
{
Success,
LockedOut,
RequiresTwoFactorAuthentication,
Failure
}
public async Task<SignInStatus> TwoFactorSignIn(string provider, string code, bool isPersistent, bool rememberBrowser)
{
var userId = await GetVerifiedUserIdAsync();
if (userId == null)
{
return SignInStatus.Failure;
}
var user = await _appUserManager.FindByIdAsync(userId);
if (user == null)
{
return SignInStatus.Failure;
}
if (await _appUserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut;
}
if (await _appUserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code))
{
// When token is verified correctly, clear the access failed count used for lockout
await _appUserManager.ResetAccessFailedCountAsync(user.Id);
await SignInAsync(user, isPersistent, rememberBrowser);
return SignInStatus.Success;
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
await _appUserManager.AccessFailedAsync(user.Id);
return SignInStatus.Failure;
}
public async Task<SignInStatus> ExternalSignIn(ExternalLoginInfo loginInfo, bool isPersistent)
{
var user = await _appUserManager.FindAsync(loginInfo.Login);
if (user == null)
{
return SignInStatus.Failure;
}
if (await _appUserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut;
}
return await SignInOrTwoFactor(user, isPersistent);
}
private async Task<SignInStatus> SignInOrTwoFactor(ApplicationUser user, bool isPersistent)
{
if (await _appUserManager.GetTwoFactorEnabledAsync(user.Id) &&
!await _authManager.TwoFactorBrowserRememberedAsync(user.Id))
{
var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
_authManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, identity);
return SignInStatus.RequiresTwoFactorAuthentication;
}
await SignInAsync(user, isPersistent, false);
return SignInStatus.Success;
}
public async Task<SignInStatus> PasswordSignIn(string userName, string password, bool isPersistent, bool shouldLockout)
{
var user = await _appUserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInStatus.Failure;
}
if (await _appUserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut;
}
if (await _appUserManager.CheckPasswordAsync(user, password))
{
return await SignInOrTwoFactor(user, isPersistent);
}
if (shouldLockout)
{
// If lockout is requested, increment access failed count which might lock out the user
await _appUserManager.AccessFailedAsync(user.Id);
if (await _appUserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut;
}
}
return SignInStatus.Failure;
}
}