NoPyGod
NoPyGod

Reputation: 5067

IdentityServer4 Revoke all reference tokens for a client, except the current one

I have a single page application which is protected by IdentityServer4 reference tokens.

I expect users to login to from multiple computers/devices.

In the settings area of the app, the user can change their password. To do so, they must enter their current password, as well as the new password.

I also wish to give the user the option to "Logout all other devices and computers".

If the user ticks this option, I want to invalidate any other reference tokens that exist for this client and this user, but I do NOT want to invalidate the reference token the user is currently using.

I only want it to logout other devices and computers. The user should stay logged in on the computer they are using.

For the life of me, I cannot see a way to do this with IdentityServer4. I was thinking I could simply run a delete on the PersistedGrants table, however I have no way of knowing which of the persisted grants in this table is the one the user is currently using.

Please help!

Upvotes: 3

Views: 2315

Answers (1)

NoPyGod
NoPyGod

Reputation: 5067

I was finally able to solve this. Make sure you're using the latest version of IdentityServer, as it includes a session_id column on the PersistedGrants table. With that, the solution is clear.

When user changes password:

        if (model.EndSessions)
        {

            var currentSessionId = User.FindFirst(JwtClaimTypes.SessionId).Value;

            foreach (var grant in db.PersistedGrants.Where(pg => pg.ClientId == "the-client-name" && pg.SubjectId == user.Id.ToString() && pg.SessionId != currentSessionId).ToList())
            {
                db.PersistedGrants.Remove(grant);
            }

            db.SaveChanges();

            await userManager.UpdateSecurityStampAsync(user);

        }

The user's other tokens are now revoked.

However, the user (on their other computer/devices) will likely still have an authentication cookie, so if they were to go to the authorization endpoint they would be granted a new token without having to login again.

To prevent that, we intercept the request for a new token with a CustomProfileService, like so -

    public override async Task IsActiveAsync(IsActiveContext context)
    {

        //only run check for cookie authentication
        if (context.Subject.Identity.AuthenticationType == IdentityConstants.ApplicationScheme)
        {

            var validationResponse = await signInManager.ValidateSecurityStampAsync(context.Subject);

            if (validationResponse == null)
            {
                context.IsActive = false;
                return;
            }

            var user = await userManager.GetUserAsync(context.Subject);

            context.IsActive = user.IsActive;

        }


    }

Upvotes: 3

Related Questions