diff --git a/src/IdentityServer/Extensions/AuthenticationTicketExtensions.cs b/src/IdentityServer/Extensions/AuthenticationTicketExtensions.cs index 6ba6cfa35..dcd3c494e 100644 --- a/src/IdentityServer/Extensions/AuthenticationTicketExtensions.cs +++ b/src/IdentityServer/Extensions/AuthenticationTicketExtensions.cs @@ -156,6 +156,10 @@ public static AuthenticationTicket Deserialize(this ServerSideSession session, I properties.ExpiresUtc = null; } + // similar to the expires update above, this is needed so that the ticket will have the + // right issued timestamp when the cookie handler performs its sliding logic + properties.IssuedUtc = new DateTimeOffset(session.Renewed, TimeSpan.Zero); + return new AuthenticationTicket(user, properties, ticket.Scheme); } catch (Exception ex) diff --git a/src/IdentityServer/Stores/Default/ServerSideTicketStore.cs b/src/IdentityServer/Stores/Default/ServerSideTicketStore.cs index 156180764..b299733ab 100644 --- a/src/IdentityServer/Stores/Default/ServerSideTicketStore.cs +++ b/src/IdentityServer/Stores/Default/ServerSideTicketStore.cs @@ -177,24 +177,8 @@ public async Task> GetSessionsAsync(SessionFilt var sessions = await _store.GetSessionsAsync(filter, cancellationToken); - var results = sessions - .Select(x => new { x.Renewed, Ticket = x.Deserialize(_protector, _logger)! }) - .Where(x => x != null && x.Ticket != null) - .Select(item => new UserSession - { - SubjectId = item.Ticket.GetSubjectId(), - SessionId = item.Ticket.GetSessionId(), - DisplayName = item.Ticket.GetDisplayName(_options.ServerSideSessions.UserDisplayNameClaimType), - Created = item.Ticket.GetIssued(), - Renewed = item.Renewed, - Expires = item.Ticket.GetExpiration(), - Issuer = item.Ticket.GetIssuer(), - ClientIds = item.Ticket.Properties.GetClientList().ToList().AsReadOnly(), - AuthenticationTicket = item.Ticket - }) - .ToArray(); + return AsUserSessions(sessions); - return results; } /// @@ -204,22 +188,7 @@ public async Task> QuerySessionsAsync(SessionQuery filt var results = await _store.QuerySessionsAsync(filter, cancellationToken); - var tickets = results.Results - .Select(x => new { x.Renewed, Ticket = x.Deserialize(_protector, _logger)! }) - .Where(x => x != null && x.Ticket != null) - .Select(item => new UserSession - { - SubjectId = item.Ticket.GetSubjectId(), - SessionId = item.Ticket.GetSessionId(), - DisplayName = item.Ticket.GetDisplayName(_options.ServerSideSessions.UserDisplayNameClaimType), - Created = item.Ticket.GetIssued(), - Renewed = item.Renewed, - Expires = item.Ticket.GetExpiration(), - Issuer = item.Ticket.GetIssuer(), - ClientIds = item.Ticket.Properties.GetClientList().ToList().AsReadOnly(), - AuthenticationTicket = item.Ticket - }) - .ToArray(); + var tickets = AsUserSessions(results.Results); var result = new QueryResult { @@ -242,23 +211,26 @@ public async Task> GetAndRemoveExpiredSessionsA var sessions = await _store.GetAndRemoveExpiredSessionsAsync(count, cancellationToken); - var results = sessions - .Select(x => new { x.Renewed, Ticket = x.Deserialize(_protector, _logger)! }) + return AsUserSessions(sessions); + } + + private UserSession[] AsUserSessions(IEnumerable sessions) + { + return sessions + .Select(x => new { x.Created, Ticket = x.Deserialize(_protector, _logger)! }) .Where(x => x != null && x.Ticket != null) .Select(item => new UserSession { SubjectId = item.Ticket.GetSubjectId(), SessionId = item.Ticket.GetSessionId(), DisplayName = item.Ticket.GetDisplayName(_options.ServerSideSessions.UserDisplayNameClaimType), - Created = item.Ticket.GetIssued(), - Renewed = item.Renewed, + Created = item.Created, + Renewed = item.Ticket.GetIssued(), Expires = item.Ticket.GetExpiration(), Issuer = item.Ticket.GetIssuer(), ClientIds = item.Ticket.Properties.GetClientList().ToList().AsReadOnly(), AuthenticationTicket = item.Ticket }) .ToArray(); - - return results; } } diff --git a/test/IdentityServer.IntegrationTests/Hosting/ServerSideSessionTests.cs b/test/IdentityServer.IntegrationTests/Hosting/ServerSideSessionTests.cs index 2a0f95e37..78f5d401b 100644 --- a/test/IdentityServer.IntegrationTests/Hosting/ServerSideSessionTests.cs +++ b/test/IdentityServer.IntegrationTests/Hosting/ServerSideSessionTests.cs @@ -21,6 +21,8 @@ using Microsoft.AspNetCore.Session; using FluentAssertions.Common; using Duende.IdentityServer; +using Microsoft.AspNetCore.DataProtection; +using Duende.IdentityServer.Extensions; namespace IntegrationTests.Hosting; @@ -34,6 +36,7 @@ public class ServerSideSessionTests private ISessionManagementService _sessionMgmt; private IPersistedGrantStore _grantStore; private IRefreshTokenStore _refreshTokenStore; + private IDataProtector _protector; private MockServerUrls _urls = new MockServerUrls(); @@ -122,6 +125,7 @@ public ServerSideSessionTests() _sessionMgmt = _pipeline.Resolve(); _grantStore = _pipeline.Resolve(); _refreshTokenStore = _pipeline.Resolve(); + _protector = _pipeline.Resolve().CreateProtector("Duende.SessionManagement.ServerSideTicketStore"); } async Task IsLoggedIn() @@ -542,17 +546,26 @@ public async Task using_refresh_token_should_extend_session() RedirectUri = "https://client/callback" }); - var expiration1 = (await _sessionStore.GetSessionsAsync(new SessionFilter { SubjectId = "alice" })).Single().Expires.Value; + var ticket1 = (await _sessionStore.GetSessionsAsync(new SessionFilter { SubjectId = "alice" })).Single() + .Deserialize(_protector, null); + var expiration1 = ticket1.GetExpiration(); + var issued1 = ticket1.GetIssued(); + + await Task.Delay(1000); await _pipeline.BackChannelClient.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = IdentityServerPipeline.TokenEndpoint, ClientId = "client", RefreshToken = tokenResponse.RefreshToken }); - - var expiration2 = (await _sessionStore.GetSessionsAsync(new SessionFilter { SubjectId = "alice" })).Single().Expires.Value; - expiration2.Should().BeAfter(expiration1); + var ticket2 = (await _sessionStore.GetSessionsAsync(new SessionFilter { SubjectId = "alice" })).Single() + .Deserialize(_protector, null); + var expiration2 = ticket2.GetExpiration(); + var issued2 = ticket2.GetIssued(); + + issued2.Should().BeAfter(issued1); + expiration2.Value.Should().BeAfter(expiration1.Value); } [Fact]