Search code examples
c#asp.net-identityidentityserver4

Stop Users Concurrently Signing In Via Different Machines - Identity Server 4


I am using Identity Server 4 with the quickstart UI and a client using Cookie Authentication.

Lets say I have user A on machine A who is currently logged in via the browser. Then user A decides to go on machine B and logs into that one. As it stands, a new session cookie will be issued for user A on machine B as well as machine A.

This is fine, but I want the option to mark particular users with a flag e.g. IsConcurrent and if it is set to true, they will be given the option to either keep their existing session on machine A, or terminate it and start a new session on machine B.

I have done some reading and found references here to updating the security stamp for a user and setting the interval to zero, so it checks the security stamp in the cookie against the stored version. However, this code didn't seem to be inline with Identity Server's code. Also, it it a valid option in this case?

I also found here which mentions storing and checking the value of session IDs, but I'm not sure if this is valid either?

An initial idea was to implement some middleware that obtained the Machine ID and stored it in a table along with the user, but then I was unsure how to take something like this any further.

Any help or advice would be much appreciated.


Solution

  • Assuming cookie based authentication, you can extend the client to validate the user session provided that the client keeps track of user sessions.

    For that you can create a session manager where you add a session for the user (sub) after login, this also includes automatic login sessions (SSO). Remove one or all sessions on logout, which should also be updated on back channel logout (LogoutCallback).

    Assuming you use middleware, you can consult the session manager there and decide what to do. Make sure that the current session isn't already activated after login. It has to step through the middleware at least once. Some pseudo code to illustrate the idea:

    public Task Invoke(HttpContext context, SessionManager sessionManager)
    {
        if (context.Principal.Identity.IsAuthenticated)
        {
            var sub = context.Principal.FindFirst("sub")?.Value;
            var sid = context.Principal.FindFirst("sid")?.Value;
    
            // User is allowed when the current session is active.
            if (!sessionManager.CurrentSessionIsActive(sub, sid))
            {
                // Rewrite path if user needs and is allowed to choose: redirect to session selector or
                // Activate the current session and deactivate other sessions, if any.
                if (sessionManager.HasMultipleSessions(sub) && sessionManager.CanSelectSession(sub))
                    context.Request.Path = new PathString("/SelectSession");
                else
                    sessionManager.ActivateCurrentSession(sub, sid);
            }
        }
        return _next(context);
    }
    

    On post of the SelectSession form you can mark in the session manager which sessions are active. If the old session should be preserved, then ignore the old session (remains active) and mark the current session as active.

    Make sure to add the middleware after authenticating the user.

    Please note that for access tokens you'll need a different strategy.