JWT impersonation reverts back to original

I’ve made an impersonation function, returning a custom crafted JWT.

object Impersonate(IUserAuth impersonatedUserAuth)
        {
            var session = (CustomUserSession)GetSession();
            if (session.ImpersonatedByUserName != null)
                throw new MasterServiceException("Already impersonated.");

            var realUsername = session.UserAuthName;
            var realRoles = session.Roles;

            if (impersonatedUserAuth.Roles.Contains("Admin") && !realRoles.Contains("Admin"))
                throw new MasterServiceException("You are not allowed to impersonate this user");

            var jwtProvider = (JwtAuthProvider)AuthenticateService.GetJwtAuthProvider();

            var header = JwtAuthProvider.CreateJwtHeader(jwtProvider.HashAlgorithm);
            var body = JwtAuthProvider.CreateJwtPayload(new AuthUserSession // only base class is JWT'ed
            {
                UserAuthId = impersonatedUserAuth.Id.ToString(),
                UserName = impersonatedUserAuth.UserName,
                DisplayName = impersonatedUserAuth.DisplayName,
                Email = impersonatedUserAuth.Email,
                IsAuthenticated = true,
            },
                issuer: jwtProvider.Issuer,
                expireIn: TimeSpan.FromMinutes(1),       // impersonation shouldn't last very long
                audiences: new[] { jwtProvider.Audience },
                roles: impersonatedUserAuth.Roles,
                permissions: impersonatedUserAuth.Permissions
                );

            // manually add these to the JWT body, prefixed with http://mycompany.com, Auth0's best practice
            body.Add("http://mycompany.com/ImpersonatedByUserName", realUsername);
            body.Add("http://mycompany.com/ImpersonatedForTestOnly", realRoles.Contains("Admin") ? "false" : "true");

            var jwtToken = JwtAuthProvider.CreateJwt(header, body, jwtProvider.GetHashAlgorithm());

            

            return new HttpResult(new
            {
                userId = impersonatedUserAuth.Id,
                UserName = impersonatedUserAuth.UserName,
                DisplayName = impersonatedUserAuth.DisplayName,
                Email = impersonatedUserAuth.Email,
                roles = impersonatedUserAuth.Roles,
                permissions = impersonatedUserAuth.Permissions,
                BearerToken = jwtToken,
                realUsername = realUsername
            })
            {   // put token in Cookie
                Cookies = {
                    new System.Net.Cookie("ss-tok", jwtToken, "/") {
                        HttpOnly = true,
                        Secure = Request.IsSecureConnection,
                        Expires = DateTime.UtcNow.Add(jwtProvider.ExpireTokensIn),
                    }
                }
            };

This works beautifully, even in SS UI.

However, because of the short expiration (usually 30 min), after the token has expired (I’m using token as cookie), SS will somehow find back to a previous token. Probably this has something to do with the session.

I suspect I’ll need to do something with the session as well as just return the token cookie?

I’ve debugged and this happens:

  1. I impersonate and get back the new JWT in ss-tok
  2. After expiry, suddenly I get another JWT back (with my real user).

Sounds like you’re getting an auto refreshed token, if you’re not also recreating the Refresh Token it’ll refresh the default one.

Thanks for quick reply as always. My colleagues are very impressed with the customer support (even before we were customers, just posting on SO), and this is the talk in the office. Even at home, both my wife and daughter have heard about “Batman” for years (that’s what we call Mythz, because of the avatar).

Anyway, creating a refresh token logs me out after the expiry, which is much better, and just what I want.

I’m not using the refresh token for anything.

Just a quick question out of curiosity: If I had used JsonServiceClient like I should (instead of Axios), that would have called refresh-token somewhere. How would that have worked? I assume that ends up in JwtAuthProvider somewhere, instead of in my custom “ImpersonationServices” class… Does it create a new token, or does it refresh the signature of the existing token or what?

For reference, for other users:

            var jwtToken = JwtAuthProvider.CreateJwt(header, body, jwtProvider.GetHashAlgorithm());
            var jwtRefreshToken = jwtProvider.CreateJwtRefreshToken(impersonatedUserAuth.UserName, jwtProvider.ExpireRefreshTokensIn);

            return new HttpResult(new
            {
                userId = impersonatedUserAuth.Id,
                UserName = impersonatedUserAuth.UserName,
                DisplayName = impersonatedUserAuth.DisplayName,
                Email = impersonatedUserAuth.Email,
                roles = impersonatedUserAuth.Roles,
                permissions = impersonatedUserAuth.Permissions,
                BearerToken = jwtToken,
                RefreshToken = jwtRefreshToken,
                realUsername = realUsername
            })
            {   // put token in Cookie
                Cookies = {
                    new System.Net.Cookie("ss-tok", jwtToken, "/") {
                        HttpOnly = true,
                        Secure = Request.IsSecureConnection,
                        Expires = DateTime.UtcNow.Add(jwtProvider.ExpireTokensIn),
                    },
                    new System.Net.Cookie("ss-reftok",jwtRefreshToken, "/") {
                        HttpOnly= true,
                        Secure= Request.IsSecureConnection,
                        Expires = DateTime.UtcNow.Add(jwtProvider.ExpireRefreshTokensIn),
                    }
                }
            };

1 Like

We’ve documented the changes for v6 where the auto refresh tokens are performed on the server when using the default Token Cookies:
https://docs.servicestack.net/releases/v6#jwt-changes

Prior to v6 or if not using Token Cookies each Service Client had built-in detection + auto refresh token support:
https://docs.servicestack.net/jwt-authprovider#automatically-refreshing-access-tokens

For the best dev UX we recommend using ServiceStack’s typed Service Clients for each of its supported languages.