diff --git a/src/Identity/SignInManager.cs b/src/Identity/SignInManager.cs
index 879a38dcf6..d553cd71c9 100644
--- a/src/Identity/SignInManager.cs
+++ b/src/Identity/SignInManager.cs
@@ -89,7 +89,8 @@ namespace Microsoft.AspNetCore.Identity
///
/// The used.
///
- public HttpContext Context {
+ public HttpContext Context
+ {
get
{
var context = _context ?? _contextAccessor?.HttpContext;
@@ -257,7 +258,7 @@ namespace Microsoft.AspNetCore.Identity
/// The expected security stamp value.
/// True if the stamp matches the persisted value, otherwise it will return false.
public virtual async Task ValidateSecurityStampAsync(TUser user, string securityStamp)
- => user != null && UserManager.SupportsUserSecurityStamp
+ => user != null && UserManager.SupportsUserSecurityStamp
&& securityStamp == await UserManager.GetSecurityStampAsync(user);
///
@@ -279,7 +280,7 @@ namespace Microsoft.AspNetCore.Identity
}
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
- return attempt.Succeeded
+ return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
@@ -330,7 +331,13 @@ namespace Microsoft.AspNetCore.Identity
if (await UserManager.CheckPasswordAsync(user, password))
{
- await ResetLockout(user);
+ var alwaysLockout = AppContext.TryGetSwitch("Microsoft.AspNetCore.Identity.CheckPasswordSignInAlwaysResetLockoutOnSuccess", out var enabled) && enabled;
+ // Only reset the lockout when TFA is not enabled when not in quirks mode
+ if (alwaysLockout || !await IsTfaEnabled(user))
+ {
+ await ResetLockout(user);
+ }
+
return SignInResult.Success;
}
Logger.LogWarning(2, "User {userId} failed to provide the correct password.", await UserManager.GetUserIdAsync(user));
@@ -534,7 +541,7 @@ namespace Microsoft.AspNetCore.Identity
/// Flag indicating whether the sign-in cookie should persist after the browser is closed.
/// The task object representing the asynchronous operation containing the
/// for the sign-in attempt.
- public virtual Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent)
+ public virtual Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent)
=> ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent, bypassTwoFactor: false);
///
@@ -645,7 +652,7 @@ namespace Microsoft.AspNetCore.Identity
return IdentityResult.Success;
}
-
+
///
/// Configures the redirect URL and user identifier for the specified external login .
///
@@ -708,7 +715,12 @@ namespace Microsoft.AspNetCore.Identity
}
return identity;
}
-
+
+ private async Task IsTfaEnabled(TUser user)
+ => UserManager.SupportsUserTwoFactor &&
+ await UserManager.GetTwoFactorEnabledAsync(user) &&
+ (await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
+
///
/// Signs in the specified if is set to false.
/// Otherwise stores the for use after a two factor check.
@@ -720,10 +732,7 @@ namespace Microsoft.AspNetCore.Identity
/// Returns a
protected virtual async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, string loginProvider = null, bool bypassTwoFactor = false)
{
- if (!bypassTwoFactor &&
- UserManager.SupportsUserTwoFactor &&
- await UserManager.GetTwoFactorEnabledAsync(user) &&
- (await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0)
+ if (!bypassTwoFactor && await IsTfaEnabled(user))
{
if (!await IsTwoFactorClientRememberedAsync(user))
{
diff --git a/test/Identity.Test/SignInManagerTest.cs b/test/Identity.Test/SignInManagerTest.cs
index 4ac34d4d0c..392e0e9ac5 100644
--- a/test/Identity.Test/SignInManagerTest.cs
+++ b/test/Identity.Test/SignInManagerTest.cs
@@ -266,6 +266,64 @@ namespace Microsoft.AspNetCore.Identity.Test
auth.Verify();
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task CheckPasswordOnlyResetLockoutWhenTfaNotEnabled(bool tfaEnabled)
+ {
+ // Setup
+ var user = new PocoUser { UserName = "Foo" };
+ var manager = SetupUserManager(user);
+ manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
+ manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
+ manager.Setup(m => m.SupportsUserTwoFactor).Returns(tfaEnabled).Verifiable();
+ manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
+
+ if (tfaEnabled)
+ {
+ manager.Setup(m => m.GetTwoFactorEnabledAsync(user)).ReturnsAsync(true).Verifiable();
+ manager.Setup(m => m.GetValidTwoFactorProvidersAsync(user)).ReturnsAsync(new string[1] {"Fake"}).Verifiable();
+ }
+ else
+ {
+ manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable();
+ }
+
+ var context = new DefaultHttpContext();
+ var helper = SetupSignInManager(manager.Object, context);
+
+ // Act
+ var result = await helper.CheckPasswordSignInAsync(user, "password", false);
+
+ // Assert
+ Assert.True(result.Succeeded);
+ manager.Verify();
+ }
+
+ [Fact]
+ public async Task CheckPasswordAlwaysResetLockoutWhenQuirked()
+ {
+ AppContext.SetSwitch("Microsoft.AspNetCore.Identity.CheckPasswordSignInAlwaysResetLockoutOnSuccess", true);
+
+ // Setup
+ var user = new PocoUser { UserName = "Foo" };
+ var manager = SetupUserManager(user);
+ manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
+ manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
+ manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
+ manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable();
+
+ var context = new DefaultHttpContext();
+ var helper = SetupSignInManager(manager.Object, context);
+
+ // Act
+ var result = await helper.CheckPasswordSignInAsync(user, "password", false);
+
+ // Assert
+ Assert.True(result.Succeeded);
+ manager.Verify();
+ }
+
[Theory]
[InlineData(true)]
[InlineData(false)]
@@ -285,10 +343,7 @@ namespace Microsoft.AspNetCore.Identity.Test
manager.Setup(m => m.SupportsUserTwoFactor).Returns(true).Verifiable();
manager.Setup(m => m.GetTwoFactorEnabledAsync(user)).ReturnsAsync(true).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
- if (supportsLockout)
- {
- manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable();
- }
+ manager.Setup(m => m.GetValidTwoFactorProvidersAsync(user)).ReturnsAsync(new string[1] { "Fake" }).Verifiable();
var context = new DefaultHttpContext();
var helper = SetupSignInManager(manager.Object, context);
var auth = MockAuth(context);