Search code examples
javascriptjqueryasp.net-identityasp.net-core-2.0identityserver4

How can I safely implement idle timeout when using identityserver4 and aspnet identity?


I'm working on an identityserver4 login site (server UI) with .NET Identity in .NET Core 2.2 Razor Pages, I have a javascript modal alert that warns users of a pending idle timeout, and then when reaching timeout it redirects the user to the logout screen by setting window.location

The trouble I have is that the OnGet in the quick start sample shows a user prompt to log out as at this point logoutId is null. I want to log out without prompting the user.

For the time being I have worked around this by passing an "autoLogout" parameter to my Logout page which bypasses the check for logoutId and sets ShowLogoutPrompt = false. I'm aware that this somewhat defeats the purpose of checking for logoutId to ensure that it is safe to sign-out without prompt.

Is there a better way to do what I'm trying to do?

Edit 16 Jul 2019: It seems as though the "right" way to handle idle timeout is to set the application cookie's token expiry (to say 20 minutes) and enable SlidingExpiration so that the token is renewed when the user refreshes. For good info on this see this blog post, this github issue thread (including comments from Brock Allen), and this info in the MS docs.

My trouble is that this solution has two huge drawbacks.

  1. SlidingExpiration only refreshes the cookie if the user is >50% through the token's TimeSpan (see SlidingExpiration info in MS docs here). So if they refresh 9m59s into a 20 minute token they will timeout after just 10 minutes instead of 20. One workaround would be to set the token lifetime to 40 minutes, which would give the user at least 20 minutes of idle time, but they could have up to 40 minutes of idle time which is not acceptable.
  2. One of my requirements is a modal to warn the user of an impending timeout and give them the option to continue/log out. To do this using this cookie approach I would need to read the token expiry time from the cookie in my Javascript (or at least in my Razor Page in C#) to enable me to time when to show the warning. Even without the modal requirement I'd need to know when the token has expired so that I could cause a page refresh to send the user to the login screen. I'm attempting to read the expiry time using the following code but it fails to read the correct expiry time after a token refresh until the page is refreshed a second time, I don't know why.
    @(DateTime.Parse(((await Context.AuthenticateAsync()).Properties.Items)[".expires"]))

Another less significant drawback to the cookies approach is that if I manage to implement a modal popup and the user opts to continue, then the page will need a refresh to get a new token, at which point any unsaved data would be lost. I guess if they time out then unsaved data would be lost anyway though so this is a relatively minor point compared with the above.

I'm thinking of going back to my original solution which has the desired functionality but would be open to abuse by an attacker who noticed my autoLogout parameter in the idle timeout javascript and could then use it to provide a hotlink to the logout page. At the moment taking that risk feels like my best option.

I feel like I've been down a rabbit hole on this one and still have no good solution. It amazes me that what I imagine to be a common use case (idle timeout with a warning allowing the user to continue/log out) is so poorly catered for with this authentication technology. Am I missing something? Do I have the wrong end of the stick?


Solution

  • I'm posting my final solution here. It works but I don't like it much. For references, details on why I think it's a bit hacky, and of what I think the main drawbacks are see the 16th Jul edit to my original question above.

    In ConfigureServices after adding identityserver I set the cookie's SlidingExpiration = true; ExpireTimeSpan = AppSettings.IdleTimeoutMins (see this blog for how I set up AppSettings):

    // Rename the .AspNetCore.Identity.Application cookie and set up for idle timeout
    services.ConfigureApplicationCookie(options =>
    {
        options.Cookie.Name = "xxxx.Application";
        options.SlidingExpiration = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(_config.GetValue<int>("AppSettings:" + nameof(AppSettings.IdleTimeoutMins)));
    });
    

    I have a Partial Razor Page in which I have javascript code to display a modal alert to the user with a count down timer. I get the timeoutSeconds from AppSettings.IdleTimeoutMins and also have a setting to determine when to show the warning. For more detail on this bit (and its pros and cons) see my other question and answer here: How to get ASP.NET Identity authentication ticket expiry in Razor Page? The warning message gives the user the option to "Continue" which refreshes the page (and therefore the authentication ticket) or "Log Out", which sends them to the Log Out confirmation page. If the clock runs down then the page is refreshed, which causes them to be returned to the Log In screen.

    At the top of the Partial:

    @inject RussellLogin.Services.IAppSettings AppSettings;
    @using Microsoft.AspNetCore.Authentication;
    

    Getting the (assumed) number of seconds remaining on the ticket:

    secondsRemaining = "@(DateTime.Parse(((await AuthenticationHttpContextExtensions
                                                 .AuthenticateAsync(Context))
                                                 .Properties
                                                 .Items)[".expires"])
                         .Subtract(DateTime.Now)
                         .TotalSeconds)";
    // If secondsRemaining is less than half the expiry timespan then assume it will be re-issued
    if (secondsRemaining < timeoutSeconds / 2) {
        secondsRemaining = timeoutSeconds;
    }