When dealing with OpenID Connect (OIDC) and OAuth authentication in a modern .NET application, Identity Server is ofted used as the identity provider. In this post, I’ll work through a common, but quite specific scenario: configuring the lifetime of a client session. In this case, the user of an ASP.NET MVC Client must be logged out of the session after 15 minutes of inactivity. In such a setup, there are a lot of places where you can configure time-outs, lifetimes, expirations etc. So which ones are relevant for the client session and which ones are not? Let’s find out.

It can be difficult to get started with these technologies, because there is a lot of specific terminology to learn and there are quite a lot of ‘moving parts’ to the entire solution. I’ve included some links at the bottom of this post that I have found quite helpful as starting points for learning this material. For this article, I’ll assume you have a good grasps of the basics.

Let’s first set the stage.

Setup

In this scenario, we have three applications:

  • An ASP.NET MVC web application (the Client, in Identity Server terminology)
  • A Web API (a Resource)
  • Identity Server (the Identity Provider)

Some pages in the Client application require authentication, because they display data from the API. This API is protected, so the Client need to send a valid Access Token to get access to the APIs data. Identity Server is used as the Identity Provider. So far, a pretty standard setup.

The required behavior: a sliding session that expires after inactivity

For this scenario, let’s consider the following requirements:

  1. Users must log in to access the protected pages in the MVC client.
  2. As long as the user is active on the site, the session remains valid (i.e. we have a sliding session, it won’t expire as long as the user keeps using the client).
  3. When the user is inactive for 15 minutes, the session will expire. Any attempt to visit the protected pages after that time should fail and the user should be redirected to the login screen.
  4. When the user closes the browser, the session should be destroyed.

We’ll implement these requirements one at a time.

1. Getting the authentication to work using the hybrid flow

The first requirement is the easiest to implement. If you simply follow the quickstart tutorials in the Identity Server documentation, this is where you’ll end up. The MVC client now uses the OpenID Connect Hybrid flow to obtain the access token that it needs.

When you have implemented the quickstart scenario, note that there are certain things that we have not configured explicitly: how long is this access token valid? What happens when the token expires? How long can the user keep using the client before he needs to re-authenticate or re-login? All of this is configured of course, but all using default settings of both Identity Server and the OIDC middleware that the client uses. We’ll make some of this configuration explicit when we look at the other requirements.

2. Make sure we have a sliding session

The second requirement is to implement sliding expiration: as long as the user is actively using the Client application, the session should remain active. This one is also easy to implement, in fact, if you followed the quickstart tutorials than you have already implemented this, because a sliding expiration is the default setting for MVC sessions.

However, since this is an explicit requirement, I’d like to make it explicit in the code as well. The place to configure this is the startup configuration of the Client, where you set up the cookie middleware. This is usually in the Startup class and looks something like this:

// ..
.AddCookie(options =>
{
  // Configure the client application to use sliding sessions
  options.SlidingExpiration = true;
})
// ..

One important point to note here is the following: the session lifetime is not related to the token configuration. When I first tried to implement this, I was fiddling with token expiration times, various cookie expiration settings that can be configured in a dozen different places, but none of that is relevant. It was only when I found this comment by Brock Allen, one of the authors of Identity Server, that things became clear:

“The user authentication lifetime is really something separate from access token lifetime.”

So, when the requirements deal with user sessions, the session configuration (in this case, the cookie middleware) is the place to configure the appropriate settings.

The next requirement is to expire the session after 15 minutes of inactivity. This requirement had me confused for a while as well, as there are again a dozen different places to configure timeouts and lifetimes and what have you. At some point, I figured that it had to have something to do with the Cookie. I could think of three different places where the cookie could affect the session lifetime:

  1. The expiration time of the access token, which is received from Identity Server and stored somewhere inside the payload of the cookie.
  2. The time that specifies how long the browser will keep the cookie.
  3. The time that specifies how long the authentication ticket that is stored inside the cookie is valid.

The first one is related to the tokens. As we have learned in the previous section, the session lifetime is a separate issue that is not related to token lifetimes. So this is not the setting to fiddle with.

The second option is the expiration time of the cookie that you will see when you inspect the cookie using your browser’s Development tools. You can configure it in the OnTicketReceived event of the AddOpenIdConnect middleware, something like the following:

.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
  // ..
  options.Events.OnTicketReceived = async (context) =>
  {
    context.Properties.ExpiresUtc = DateTime.UtcNow.AddHours(1);
  };
  // ..
}

However: in our case, the last requirement states that the session should be destroyed when the user closes the browser. This means that our cookie needs to be a session cookie as opposed to a persistent cookie. Session cookies are destroyed by the browser when you close the browser window. A persistent cookie remains on the users machine even when the browser is closed. And the only thing that distinguishes a session cookie from a persistent cookie is this expiration field. The browser inspection tools will display the value of this field as “Session” or “1969-12-31T23:59:59.000Z” when it is a session cookie, or with some future timestamp in the case of a persistent cookie.

The Cookies panel in the browsers developer tools

So that leaves just on more option to control our session expiration: the lifetime of the authentication ticket that is stored inside the cookie. Just like the setting for sliding expiration, this setting is configured in the cookie middleware. So we can update our code like this:

// ..
.AddCookie(options =>
{
  // Configure the client application to use sliding sessions
  options.SlidingExpiration = true;
  // Expire the session of 15 minutes of inactivity
  options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
})
// ..

When I first configured this, I wrongly assumed that this would set the expiration of the cookie itself, as reflected in the browsers development tools. However, this is in fact a setting of the ticket that is stored inside the cookie, not of the cookie itself. It is this ticket that is evaluated by the MVC client whenever a request is handled. This ticket determines the validity of the users authentication session.

So even when this ticket inside the cookie has expired, the browser will still send this cookie to the server, because the cookie itself has not expired. But this is not a problem: the middleware that runs on the server will detect that the ticket is no longer valid and simply reject and ignore it. This will trigger another part of the middleware that will automatically redirect the user to the login page of Identity Server.

Conclusion

As it turned out, we hardly had to deal with Identity Server at all to meet the requirements for the user sessions. And let that be the main take-away from this post: that user session management and Identity Server configuration are two mostly unrelated subjects. Knowing the distinction makes it much easier to navigate your way through all the available configuration options.