DuendeSoftware / Support

Support for Duende Software products
20 stars 0 forks source link

ASP.NET sliding cookie expiration logic breaks with server-side sessions and a persistent cookie #1417

Open RandScullard opened 1 week ago

RandScullard commented 1 week ago

Which version of Duende IdentityServer are you using? 7.0.5

Which version of .NET are you using? 8.0.8

Describe the bug

We have server-side sessions enabled in Identity Server. We have a client-side app that uses OIDC to get tokens from Identity Server. The Client is configured with:

RefreshTokenExpiration = TokenExpiration.Sliding

So every time the client-side app refreshes the access token, the expiration of the refresh token and the user's server-side session in Identity Server are both pushed ahead in time. This worked great until we tried using a persistent cookie for the user's Identity Server login session (the "remember me" option on the login page). Then we noticed that the expiration of the .AspNetCore.Identity.Application cookie (the cookie itself, not the expiration of the ticket inside the cookie) never advances, and the cookie eventually expires, even though the refresh token periodically expires and the user's browser is redirected to Identity Server to get a new one.

We took a look at the code in ASP.NET for refreshing the auth cookie and we see why this behavior is happening. Please refer to https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/src/CookieAuthenticationHandler.cs, specifically the CheckForRefreshAsync function. The logic in this function looks at the issue and expiration timestamps in the ticket and decides to refresh the cookie if the current time is closer to the expiration time than to the issue time (more than halfway through the lifetime). The problem here is that Identity Server's server-side sessions feature is advancing the issue and expiration timestamps in the ticket every time our client-side app uses the refresh token. As long as this keeps happening, CheckForRefreshAsync will never find that it is more than halfway through the ticket lifetime, and therefore will never decide to send a new auth cookie to the user's browser. Ultimately, the cookie will expire when it should have been kept alive.

It looks as if CheckForRefreshAsync assumes that the issue and expiration timestamps reflect the actual times the cookie was issued and will expire, but the behavior of Identity Server server-side sessions breaks this assumption - in this case the timestamps reflect the ticket lifetime, which is out of sync with the cookie lifetime. (This is only a problem when the cookie is persistent, since session cookies have no expiration date.)

We hope you can provide some guidance on how to deal with this!

AndersAbel commented 21 hours ago

Thank you for your detailed report. It looks like you might have found a combination of settings that we didn't consider when designing the solution.

If all of these features are used on the same time, I don't think we handle this correctly.

We will investigate and consider if there's something we need to fix in our implementation.