Introducing Server-Side Sessions
Open.IdentityServer has always needed to track a login session to issue front-channel access tokens. It has kept this state in an HTTP Cookie. At the simplest level, it only needed to track the subject identifier of the user who owns the session. However, the required state grew over time. For example, supporting back-channel logout required the session to track all clients the user may have an active session with. This increased the cookie size, which can lead to:
- Increased network overhead as cookies are sent with every relevant HTTP request.
- Browser limits, with individual cookies typically limited to around 4 KB.
- Larger HTTP headers, which can exceed limits imposed by web servers, proxies, or load balancers.
- Cookie chunking, which avoids individual cookie limits but can further increase header size.
- Poor scalability for authentication state, particularly when storing large numbers of claims or permissions.
Server-side sessions address this by keeping the cookie a consistent size: it stores the growing session state on the server and include only a key in the cookie. This keeps the cookie size consistent.
In addition, moving state to the server side provides greater visibility into users' sessions. For example, an admin can see all active sessions. It also lets them revoke sessions without any browser involvement. If a user resets their credentials because they fear their account has been compromised, back-channel logout can remove all sessions across all their devices.
Enabling Server-Side Sessions
It's as simple as adding AddServerSideSessions to the DI build-up statements
var idsrvBuilder = builder.Services.AddIdentityServer().AddServerSideSessions()
If you are using Open.IdentityServer Entity Framework assembly or the default InMemory Operational store (not for production) to provide the Operational Store, you don't need to do anything else. If you have your own custom implementation of the Operational Store, you will need to provide an implementation of IServerSideSessionStore.
There are various configuration options; see the docs for going beyond the defaults. Once enabled, there is no obvious difference, other than a reduced cookie size. However, before server-side sessions, the first application sign-in resulted in a 582-byte cookie; after the second application signed in, it increased to 624 bytes. With server-side sessions enabled, the cookie was 347 bytes and remained the same size after the second application signed in.
Working with Server-Side Sessions
Before Server-Side Sessions, there was no way to see all active IdentityServer sessions. Server-side sessions provide an API to query and revoke sessions. The ISessionManagementService interface defines the API.
public interface ISessionManagementService
{
/// <summary>
/// Method for querying sessions
/// </summary>
/// <param name="filter">filter to be used</param>
/// <param name="ct">cancellation token</param>
/// <returns>paginated query result of user sessions</returns>
Task<QueryResult<UserSession>> QuerySessionsAsync(SessionQuery? filter, CancellationToken ct = default);
/// <summary>
/// Method for removing sessions
/// </summary>
/// <param name="context">remove session context</param>
/// <param name="ct">cancellation token</param>
/// <returns>void</returns>
Task RemoveSessionsAsync(RemoveSessionsContext context, CancellationToken ct = default);
}
Querying Sessions
The QuerySessionsAsync method lets you build a dashboard for users or admins to view active sessions. An example of this is in the sample, to see it in action, open the solution in your IDE andĀ
- Run the IdentityServer
- Run MVCClient, it will navigate to IdentityServer to sign-in, use usernameĀ bob and password bob
- Now launch AnotherMVC app; you will be automatically signed in
- Now, in the tab showing IdentityServer goto https://localhost:5001/Sessions, you will see your session with both applications associated with the session
Inside the IdentityServer project, open Controllers/Sessions.cs to see its use of the Query API. There are two GET methods. One lists all sessions; the second allows filtering by username
Open a second browser (different type or an incognito window), launch one of the client applications (https://localhost:5002 or 5003) and sign in as Alice, then refresh the Sessions view; you will now see both Bob and Alice sessions.
Ending Sessions
With traditional cookie-based sessions, you had to log out from the browser bound to the session to remove sessions and initiate back-channel logout. The advantage of Server-Side Sessions is that you can remove them without access to the browser bound to the session.
The RemoveSessionsAsync method lets you remove sessions by providing selection criteria using a RemoveSessionsContext object. The code below removes any session associated with the user
RemoveSessionsContext ctx = new RemoveSessionsContext()
{
SubjectId = user.SubjectId,
SendBackchannelLogoutNotification = true,
RevokeConsents = false,
RevokeTokens = true
};
await serverSessions.RemoveSessionsAsync(ctx);
The Sessions Controller has a Delete method you can invoke via CURL or any other HTTP client application at the command line.
curl -X DELETE https://localhost:5001/sessions/bob
Refreshing the session list in IdentityServer will now not have any sessions for bob
Refreshing in any client application signed in as Bob redirects you to IdentityServer for re-authentication; this demonstrates back-channel logout when ending a session.
Summary
Server-side sessions are a big step forward in version 3; they prevent cookie bloat and let you manage sessions centrally. When a user resets their password or second factor, you can now log them out of all their sessions across all devices.