Admin login as specific user

Hi,

I have created a SAAS app which allows login as a specific user, using the Multitenant approach here http://docs.servicestack.net/multitenancy. My question is how do I provide a feature where the system admin can login as a specific tennant user? The tennant users will be required to agree to terms and conditions allowing the admin to login to provide system support. Once logged in, the admin will assume the identity of the chosen user, have the same role and permissions, and be able to access the tennant’s data. Any actions will be logged as .

Any advice on a suitable pattern would be appreciated.

Thanks

1 Like

One option is to enable SkipPasswordVerificationForInProcessRequests in the CredentialsAuthProvider which will allow you to create a Service that lets you authenticate with just a UserName since the CredentialsAuthProvider will ignore passwords when authenticating with an internal request, e.g:

[RequiredRole("Admin")]
[Restrict(InternalOnly=true)]
public class ImpersonateUser 
{
    public string UserName { get; set; }
}

public object Any(ImpersonateUser request)
{
    using (var service = base.ResolveService<AuthenticateService>()) //In Process
    {
        service.Post(new Authenticate { provider = "logout" });
        
        return service.Post(new Authenticate {
            provider = AuthenticateService.CredentialsProvider,
            UserName = request.UserName,
        });
    }
}

A more flexible option is to enable the JWT AuthProvider which will let you manually create a JWT Token where you can generate a Custom UserSession with an existing UserId, Roles, Permissions, etc.

Alternatively you can create a Custom AuthProvider where you can control who and how users can login and what UserSession is created.

I have implemented the above code and in the ViewPage.UserSession shows the appropriate impersonated User. However any service code that calls GetSession().UserAuthId is still pulling up the user who logged in with a username and password. What am I doing wrong?

I am reviewing the AuthUserSession I and see the DisplayName, Email and Username changing to the new user, but UserAuthId and UserAuthName are unchanged.

Are you using a Custom AuthProivder or anything else non-standard?

Pretty much a copy and past from your example code, with the api provider added.

        public void Configure(IAppHost appHost)
    {
        var appSettings = appHost.AppSettings;
        appHost.Plugins.Add(new AuthFeature
        (() => new AuthUserSession(),
            
            new IAuthProvider[] {
                new CredentialsAuthProvider(appSettings){SkipPasswordVerificationForInProcessRequests = true}, 
                new ApiKeyAuthProvider(appSettings){RequireSecureConnection = false }, 
            })
        {
            GenerateNewSessionCookiesOnAuthentication = false
            , HtmlLogoutRedirect = "/login"
            , IncludeRegistrationService = true,
        });
        
        //override the default registration validation with your own custom implementation
        appHost.RegisterAs<CustomRegistrationValidator, IValidator<Register>>();

        var authRepository = new OrmLiteAuthRepository(appHost.Resolve<IDbConnectionFactory>());

        if (!authRepository.hasInitSchema)
        {
            authRepository.InitSchema();
            authRepository.InitApiKeySchema();
        }
        
        if (authRepository.GetUserAuthByUserName("admin")== null)
        {
             GenerateAdminUser(appHost);
        }
        
    }

ok this has the behavior as authenticating whilst you’re still signed in (merging sign-ins), for a clean sign in you should logout first, e.g:

public object Any(ImpersonateUser request)
{
    using (var service = base.ResolveService<AuthenticateService>()) //In Process
    {
        service.Post(new Authenticate { provider = "logout" });
        
        return service.Post(new Authenticate {
            provider = AuthenticateService.CredentialsProvider,
            UserName = request.UserName,
        });
    }
}

A different approach could be to have a service that:

  1. Save your admin user’s current session cookie ss-id to a temporary session cookie
  2. Get the user session id from cache that you want to impersonate
  3. Set that user’s session id cookie ss-id over your admin’s ss-id
  4. To reverse, assign the stored admin’s original session over the impersonated user’s cookie id.

Note: if they log out then it also ends your session.
Note 2: I didn’t read the whole thread so not sure if there were some other requirements.

Update: With this both the admin and the user can be signed in at the same time, but as @mythz noted below, yeah you would need to scan the sessions in cache and if you have a lot of users it may not work efficiently. You would also have to build an interface to select/search for the correct user.

Seems like a lot of work and scanning sessions is not straight forward and it’s not going to be possible if the user you’re trying to impersonate isn’t authenticated as they’re not going to have a session you can hijack.

The impersonate Service above is just a single HTTP Call, so it’s simpler to impersonate however the most flexible option is to create a script to generate a JWT with the user and Roles/Permissions you want that you can can authenticate on the QueryString with?ss-tok={jwt} after enabling it with:

new JwtAuthProvider(...) { AllowInQueryString = true }

Note this method just redirected me to the login page after it logged out the user.