6

Given a username and password how do i impersonate that user and run some code as that user.

And by managed i mean without pinvokes or dllimports

Simon
  • 33,714
  • 21
  • 133
  • 202

5 Answers5

17

This is the wrapper class we created that has worked on several different Windows platforms:

public class Impersonator
{
    // constants from winbase.h
    public const int LOGON32_LOGON_INTERACTIVE = 2;
    public const int LOGON32_LOGON_NETWORK = 3;
    public const int LOGON32_LOGON_BATCH = 4;
    public const int LOGON32_LOGON_SERVICE = 5;
    public const int LOGON32_LOGON_UNLOCK = 7;
    public const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
    public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;

    public const int LOGON32_PROVIDER_DEFAULT = 0;
    public const int LOGON32_PROVIDER_WINNT35 = 1;
    public const int LOGON32_PROVIDER_WINNT40 = 2;
    public const int LOGON32_PROVIDER_WINNT50 = 3;

    [DllImport("advapi32.dll", SetLastError=true)]
    public static extern int LogonUserA(String lpszUserName, 
        String lpszDomain,
        String lpszPassword,
        int dwLogonType, 
        int dwLogonProvider,
        ref IntPtr phToken);
    [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    public static extern int DuplicateToken(IntPtr hToken, 
        int impersonationLevel,  
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    public static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet=CharSet.Auto)]
    public static extern  bool CloseHandle(IntPtr handle);

    public static WindowsImpersonationContext LogOn(string userName, string password)
    {
        return LogOn(userName, password, "");
    }

    public static WindowsImpersonationContext LogOn(string userName, string password, string domain)
    {
        WindowsIdentity tempWindowsIdentity;
        WindowsImpersonationContext impersonationContext;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        if(RevertToSelf())
        {
            if (LogonUserA(userName, domain, password, LOGON32_LOGON_NEW_CREDENTIALS,
                LOGON32_PROVIDER_DEFAULT, ref token) != 0)
            {
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                {
                    tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                    impersonationContext = tempWindowsIdentity.Impersonate();
                    if (impersonationContext != null)
                    {
                        CloseHandle(token);
                        CloseHandle(tokenDuplicate);
                        return impersonationContext;
                    }
                }
            }
            else
            {
                var win32 = new Win32Exception(Marshal.GetLastWin32Error());
                //throw new Exception(string.Format("{0}, Domain:{1}, User:{2}, Password:{3}",
                //    win32.Message, domain, userName, password));
                throw new Exception(win32.Message);
            }
        }
        if(token!= IntPtr.Zero)
            CloseHandle(token);
        if(tokenDuplicate!=IntPtr.Zero)
            CloseHandle(tokenDuplicate);
        return null; // Failed to impersonate
    }

    public static bool LogOff(WindowsImpersonationContext context)
    {
        bool result = false;
        try
        {
            if (context != null)
            {
                context.Undo();
                result = true;
            }
        }
        catch
        {
            result = false;
        }
        return result;
    }
}
John Kaster
  • 2,509
  • 1
  • 33
  • 40
  • Where did you find the LOGON32_PROVIDER_x values? – Micah B. Oct 06 '14 at 20:37
  • 1
    @MicahBurnett it's been a while since I looked at this code, but the comment says I got it from winbase.h. An MSDN search or winbase.h search should give you some refs. There's also this http://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx – John Kaster Oct 06 '14 at 20:48
  • @MicahB.you can find with they integer values in here https://learn.microsoft.com/en-us/windows/win32/secauthn/logonuserexexw – Sergio Rezende Jul 24 '23 at 16:04
3

I've boiled it down to two simple methods:

public bool ImpersonateValidUser(String userName, String domain, String password)
public void UndoImpersonation()

You can directlyh copy/paste the class below and use it in your project:

    class ImpersonationContext
    {
        [DllImport("advapi32.dll")]
        public static extern int LogonUserA(String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int DuplicateToken(IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool RevertToSelf();

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool CloseHandle(IntPtr handle);

        public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
        public const int LOGON32_LOGON_INTERACTIVE = 2;
        public const int LOGON32_PROVIDER_DEFAULT = 0;
        public const int LOGON32_PROVIDER_WINNT50 = 3;
        WindowsImpersonationContext impersonationContext;

        public bool ImpersonateValidUser(String userName, String domain, String password)
        {
            WindowsIdentity tempWindowsIdentity;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            if (RevertToSelf())
            {
                if (LogonUserA(userName, domain, password, LOGON32_LOGON_NEW_CREDENTIALS,
                    LOGON32_PROVIDER_WINNT50, ref token) != 0)
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                        if (impersonationContext != null)
                        {
                            CloseHandle(token);
                            CloseHandle(tokenDuplicate);
                            return true;
                        }
                    }
                }
            }
            if (token != IntPtr.Zero)
                CloseHandle(token);
            if (tokenDuplicate != IntPtr.Zero)
                CloseHandle(tokenDuplicate);
            return false;
        }

        public void UndoImpersonation()
        {
            impersonationContext.Undo();
        }
    }
Silas Hansen
  • 1,669
  • 2
  • 17
  • 23
  • 1
    Sure, it boils down to two methods. Yet, your code has no error handling, and if an error does occur, does not release the handles it created. – AMissico May 12 '10 at 17:55
2

All of the examples I've seen fail to take into account the fact that the logon type is not a one size fits all solution.

For example this will only work if the user you are impersonating has permission to logon to the target system. Not always the case when accessing a remote SQL Server box. LOGON32_LOGON_INTERACTIVE

NetworkClearText is the only one that works consistently for use with SQL Server connections. - No clear text does not mean it is passing credentials in an insecure manner.

When on a workgroup and you need to impersonate a domain user NewCredentials is the one that works. (not tested with SQL Server connections)

1

There's a similar question with a great answer here.

Have a look at WindowsImpersonationContext for some more information (also there's another great code sample there)

Community
  • 1
  • 1
Codesleuth
  • 10,321
  • 8
  • 51
  • 71
  • 2
    If it's an ASP.NET website, then yes it can be done by setting the `` element in the `web.config`. Otherwise you'll have to use imports :( – Codesleuth May 11 '10 at 08:31
  • @Simon: Impersonation is also helpful during installations or for running an application "as" someone else, such as an Administrator. – AMissico May 12 '10 at 18:01
0

See ImpersonationHelper of Is it possible to safely get a SecureString value from VB .NET?. The code is production ready and robust.

It supports IDisposable, contains a RunAs method (which is invaluable), passwords are handled as SecureString, and other little helpful goodies. I also provided the testing code for both the ImpersonationHelper class with is extremely helpful for troubleshooting, and their are SecureString extenstion methods that come in handy.

Community
  • 1
  • 1
AMissico
  • 21,470
  • 7
  • 78
  • 106