diff --git a/src/Identity/SignInManager.cs b/src/Identity/SignInManager.cs
index edef639426..f805e596ab 100644
--- a/src/Identity/SignInManager.cs
+++ b/src/Identity/SignInManager.cs
@@ -258,8 +258,9 @@ 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
- && securityStamp == await UserManager.GetSecurityStampAsync(user);
+ => user != null &&
+ // Only validate the security stamp if the store supports it
+ (!UserManager.SupportsUserSecurityStamp || securityStamp == await UserManager.GetSecurityStampAsync(user));
///
/// Attempts to sign in the specified and combination
diff --git a/test/Identity.Test/SecurityStampValidatorTest.cs b/test/Identity.Test/SecurityStampValidatorTest.cs
index fe8d15a274..8bcf985540 100644
--- a/test/Identity.Test/SecurityStampValidatorTest.cs
+++ b/test/Identity.Test/SecurityStampValidatorTest.cs
@@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Identity.Test
contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object);
var signInManager = new Mock>(userManager.Object,
contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock().Object);
- signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).ReturnsAsync(shouldStampValidate ? user : default(PocoUser)).Verifiable();
+ signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).ReturnsAsync(shouldStampValidate ? user : default).Verifiable();
if (shouldStampValidate)
{
@@ -147,6 +147,50 @@ namespace Microsoft.AspNetCore.Identity.Test
});
}
+ [Fact]
+ public async Task OnValidateIdentityAcceptsWhenStoreDoesNotSupportSecurityStamp()
+ {
+ var user = new PocoUser("test");
+ var httpContext = new Mock();
+
+
+ var userManager = MockHelpers.MockUserManager();
+
+ var claimsManager = new Mock>();
+ var identityOptions = new Mock>();
+ identityOptions.Setup(a => a.Value).Returns(new IdentityOptions());
+ var options = new Mock>();
+ options.Setup(a => a.Value).Returns(new SecurityStampValidatorOptions { ValidationInterval = TimeSpan.Zero });
+ var contextAccessor = new Mock();
+ contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object);
+ var signInManager = new SignInManager(userManager.Object,
+ contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock().Object);
+ userManager.Setup(u => u.GetUserAsync(It.IsAny())).ReturnsAsync(user).Verifiable();
+ claimsManager.Setup(c => c.CreateAsync(user)).ReturnsAsync(new ClaimsPrincipal()).Verifiable();
+
+ var services = new ServiceCollection();
+ services.AddSingleton(options.Object);
+ services.AddSingleton(signInManager);
+ services.AddSingleton(new SecurityStampValidator(options.Object, signInManager, new SystemClock()));
+ httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider());
+
+ var tid = new ClaimsIdentity(IdentityConstants.ApplicationScheme);
+ tid.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
+ var ticket = new AuthenticationTicket(new ClaimsPrincipal(tid),
+ new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow.AddSeconds(-1) },
+ IdentityConstants.ApplicationScheme);
+
+ var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityConstants.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(), new CookieAuthenticationOptions(), ticket);
+ Assert.NotNull(context.Properties);
+ Assert.NotNull(context.Options);
+ Assert.NotNull(context.Principal);
+ await SecurityStampValidator.ValidatePrincipalAsync(context);
+ Assert.NotNull(context.Principal);
+
+ userManager.VerifyAll();
+ claimsManager.VerifyAll();
+ }
+
[Fact]
public async Task OnValidateIdentityRejectsWhenNoIssuedUtc()
{