diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs index 7cef596140..82a47ecaeb 100644 --- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs @@ -21,9 +21,9 @@ namespace Microsoft.AspNet.Authentication.Cookies private const string HeaderValueMinusOne = "-1"; private const string SessionIdClaim = "Microsoft.AspNet.Authentication.Cookies-SessionId"; - private bool _shouldRenew; - private DateTimeOffset? _renewIssuedUtc; - private DateTimeOffset? _renewExpiresUtc; + private bool _shouldRefresh; + private DateTimeOffset? _refreshIssuedUtc; + private DateTimeOffset? _refreshExpiresUtc; private string _sessionKey; private Task _readCookieTask; @@ -37,6 +37,33 @@ namespace Microsoft.AspNet.Authentication.Cookies return _readCookieTask; } + private void CheckForRefresh(AuthenticationTicket ticket) + { + var currentUtc = Options.SystemClock.UtcNow; + var issuedUtc = ticket.Properties.IssuedUtc; + var expiresUtc = ticket.Properties.ExpiresUtc; + var allowRefresh = ticket.Properties.AllowRefresh ?? true; + if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration && allowRefresh) + { + var timeElapsed = currentUtc.Subtract(issuedUtc.Value); + var timeRemaining = expiresUtc.Value.Subtract(currentUtc); + + if (timeRemaining < timeElapsed) + { + RequestRefresh(ticket); + } + } + } + + private void RequestRefresh(AuthenticationTicket ticket) + { + _shouldRefresh = true; + var currentUtc = Options.SystemClock.UtcNow; + _refreshIssuedUtc = currentUtc; + var timeSpan = ticket.Properties.ExpiresUtc.Value.Subtract(ticket.Properties.IssuedUtc.Value); + _refreshExpiresUtc = currentUtc.Add(timeSpan); + } + private async Task ReadCookieTicket() { var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName); @@ -79,20 +106,7 @@ namespace Microsoft.AspNet.Authentication.Cookies return AuthenticateResult.Fail("Ticket expired"); } - var allowRefresh = ticket.Properties.AllowRefresh ?? true; - if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration && allowRefresh) - { - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - var timeRemaining = expiresUtc.Value.Subtract(currentUtc); - - if (timeRemaining < timeElapsed) - { - _shouldRenew = true; - _renewIssuedUtc = currentUtc; - var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); - _renewExpiresUtc = currentUtc.Add(timeSpan); - } - } + CheckForRefresh(ticket); // Finally we have a valid ticket return AuthenticateResult.Success(ticket); @@ -116,7 +130,7 @@ namespace Microsoft.AspNet.Authentication.Cookies if (context.ShouldRenew) { - _shouldRenew = true; + RequestRefresh(result.Ticket); } return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme)); @@ -144,7 +158,7 @@ namespace Microsoft.AspNet.Authentication.Cookies protected override async Task FinishResponseAsync() { // Only renew if requested, and neither sign in or sign out was called - if (!_shouldRenew || SignInAccepted || SignOutAccepted) + if (!_shouldRefresh || SignInAccepted || SignOutAccepted) { return; } @@ -153,13 +167,13 @@ namespace Microsoft.AspNet.Authentication.Cookies var ticket = (await HandleAuthenticateOnceAsync())?.Ticket; if (ticket != null) { - if (_renewIssuedUtc.HasValue) + if (_refreshIssuedUtc.HasValue) { - ticket.Properties.IssuedUtc = _renewIssuedUtc; + ticket.Properties.IssuedUtc = _refreshIssuedUtc; } - if (_renewExpiresUtc.HasValue) + if (_refreshExpiresUtc.HasValue) { - ticket.Properties.ExpiresUtc = _renewExpiresUtc; + ticket.Properties.ExpiresUtc = _refreshExpiresUtc; } if (Options.SessionStore != null && _sessionKey != null) @@ -175,9 +189,9 @@ namespace Microsoft.AspNet.Authentication.Cookies var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding()); var cookieOptions = BuildCookieOptions(); - if (ticket.Properties.IsPersistent && _renewExpiresUtc.HasValue) + if (ticket.Properties.IsPersistent && _refreshExpiresUtc.HasValue) { - cookieOptions.Expires = _renewExpiresUtc.Value.ToUniversalTime().DateTime; + cookieOptions.Expires = _refreshExpiresUtc.Value.ToUniversalTime().DateTime; } Options.CookieManager.AppendResponseCookie( diff --git a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs index 329b0d176e..ffcf6d8792 100644 --- a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs @@ -580,6 +580,61 @@ namespace Microsoft.AspNet.Authentication.Cookies Assert.Null(FindClaimValue(transaction5, ClaimTypes.Name)); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ShouldRenewUpdatesIssuedExpiredUtc(bool sliding) + { + var clock = new TestClock(); + DateTimeOffset? lastValidateIssuedDate = null; + DateTimeOffset? lastExpiresDate = null; + var server = CreateServer(options => + { + options.SystemClock = clock; + options.ExpireTimeSpan = TimeSpan.FromMinutes(10); + options.SlidingExpiration = sliding; + options.Events = new CookieAuthenticationEvents + { + OnValidatePrincipal = ctx => + { + lastValidateIssuedDate = ctx.Properties.IssuedUtc; + lastExpiresDate = ctx.Properties.ExpiresUtc; + ctx.ShouldRenew = true; + return Task.FromResult(0); + } + }; + }, + context => + context.Authentication.SignInAsync("Cookies", + new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))))); + + var transaction1 = await SendAsync(server, "http://example.com/testpath"); + + var transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + Assert.NotNull(transaction2.SetCookie); + Assert.Equal("Alice", FindClaimValue(transaction2, ClaimTypes.Name)); + + Assert.NotNull(lastValidateIssuedDate); + Assert.NotNull(lastExpiresDate); + + var firstIssueDate = lastValidateIssuedDate; + var firstExpiresDate = lastExpiresDate; + + clock.Add(TimeSpan.FromMinutes(1)); + + var transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction2.CookieNameValue); + Assert.NotNull(transaction3.SetCookie); + Assert.Equal("Alice", FindClaimValue(transaction3, ClaimTypes.Name)); + + clock.Add(TimeSpan.FromMinutes(2)); + + var transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction3.CookieNameValue); + Assert.NotNull(transaction4.SetCookie); + Assert.Equal("Alice", FindClaimValue(transaction4, ClaimTypes.Name)); + + Assert.NotEqual(lastValidateIssuedDate, firstIssueDate); + Assert.NotEqual(firstExpiresDate, lastExpiresDate); + } [Fact] public async Task CookieExpirationCanBeOverridenInEvent()