.NET (C#) Impersonation with Network Credentials


I required a C# class to enable ad-hoc user account impersonation for accessing resources both on the local machine and also on network machines, which I’ve reproduced here.

Of note, if you require impersonation in order to access network resources, you would intuitively select the logon type of LOGON32_LOGON_NETWORK, this however doesn’t work, as according to MSDN, this type of logon is used for fast authentication where the credentials are not added to the local credential cache.

If you require the impersonated logon to have network credentials, you must select LOGON32_LOGON_NEW_CREDENTIALS as your logon type, which requires that you select LOGON32_PROVIDER_WINNT50 as the logon provider type.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace Tools.Network
{
 public enum LogonType
 {
  LOGON32_LOGON_INTERACTIVE = 2,
  LOGON32_LOGON_NETWORK = 3,
  LOGON32_LOGON_BATCH = 4,
  LOGON32_LOGON_SERVICE = 5,
  LOGON32_LOGON_UNLOCK = 7,
  LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
  LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
 };

 public enum LogonProvider
 {
  LOGON32_PROVIDER_DEFAULT = 0,
  LOGON32_PROVIDER_WINNT35 = 1,
  LOGON32_PROVIDER_WINNT40 = 2,
  LOGON32_PROVIDER_WINNT50 = 3
 };

 public enum ImpersonationLevel
 {
  SecurityAnonymous = 0,
  SecurityIdentification = 1,
  SecurityImpersonation = 2,
  SecurityDelegation = 3
 }

 class Win32NativeMethods
 {
  [DllImport("advapi32.dll", SetLastError = true)]
  public static extern int LogonUser( 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);
 }

 /// <summary>
 /// Allows code to be executed under the security context of a specified user account.
 /// </summary>
 /// <remarks> 
 ///
 /// Implements IDispose, so can be used via a using-directive or method calls;
 ///  ...
 ///
 ///  var imp = new Impersonator( "myUsername", "myDomainname", "myPassword" );
 ///  imp.UndoImpersonation();
 ///
 ///  ...
 ///
 ///   var imp = new Impersonator();
 ///  imp.Impersonate("myUsername", "myDomainname", "myPassword");
 ///  imp.UndoImpersonation();
 ///
 ///  ...
 ///
 ///  using ( new Impersonator( "myUsername", "myDomainname", "myPassword" ) )
 ///  {
 ///   ...
 ///   
 ///   ...
 ///  }
 ///
 ///  ...
 /// </remarks>
 public class Impersonator : IDisposable
 {
  private WindowsImpersonationContext _wic;

  /// <summary>
  /// Begins impersonation with the given credentials, Logon type and Logon provider.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  ///
<param name="logonType">Type of the logon.</param>
  ///
<param name="logonProvider">The logon provider. <see cref="Mit.Sharepoint.WebParts.EventLogQuery.Network.LogonProvider"/></param>
  public Impersonator(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
  {
   Impersonate(userName, domainName, password, logonType, logonProvider);
  }

  /// <summary>
  /// Begins impersonation with the given credentials.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  public Impersonator(string userName, string domainName, string password)
  {
   Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT);
  }

  /// <summary>
  /// Initializes a new instance of the <see cref="Impersonator"/> class.
  /// </summary>
  public Impersonator()
  {}

  /// <summary>
  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  /// </summary>
  public void Dispose()
  {
   UndoImpersonation();
  }

  /// <summary>
  /// Impersonates the specified user account.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  public void Impersonate(string userName, string domainName, string password)
  {
   Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT);
  }

  /// <summary>
  /// Impersonates the specified user account.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  ///
<param name="logonType">Type of the logon.</param>
  ///
<param name="logonProvider">The logon provider. <see cref="Mit.Sharepoint.WebParts.EventLogQuery.Network.LogonProvider"/></param>
  public void Impersonate(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
  {
   UndoImpersonation();

   IntPtr logonToken = IntPtr.Zero;
   IntPtr logonTokenDuplicate = IntPtr.Zero;
   try
   {
    // revert to the application pool identity, saving the identity of the current requestor
    _wic = WindowsIdentity.Impersonate(IntPtr.Zero);

    // do logon & impersonate
    if (Win32NativeMethods.LogonUser(userName,
        domainName,
        password,
        (int)logonType,
        (int)logonProvider,
        ref logonToken) != 0)
    {
     if (Win32NativeMethods.DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0)
     {
      var wi = new WindowsIdentity(logonTokenDuplicate);
      wi.Impersonate(); // discard the returned identity context (which is the context of the application pool)
     }
     else
      throw new Win32Exception(Marshal.GetLastWin32Error());
    }
    else
     throw new Win32Exception(Marshal.GetLastWin32Error());
   }
   finally
   {
    if (logonToken != IntPtr.Zero)
     Win32NativeMethods.CloseHandle(logonToken);

    if (logonTokenDuplicate != IntPtr.Zero)
     Win32NativeMethods.CloseHandle(logonTokenDuplicate);
   }
  }

  /// <summary>
  /// Stops impersonation.
  /// </summary>
  private void UndoImpersonation()
  {
   // restore saved requestor identity
   if (_wic != null)
    _wic.Undo();
   _wic = null;
  }
 }
}

Published by

Phil Harding

SharePoint Consultant, Developer, Father, Husband and Climber.

59 thoughts on “.NET (C#) Impersonation with Network Credentials

  1. Thanks for the post, great construct, but I think it has some things wrong.

    Where you have
    _wic = WindowsIdentity.Impersonate(IntPtr.Zero);
    can you kindly tell us how that works? _wic isn’t defined and needs a ContentType, but also, you can’t impersonate an empty token so far as I know (had code give me an error to that effect once, I thought).

    “var wi” also has issues. Can’t use “var” in C# – needs to be
    WindowsIdentity wi = new WindowsIdentity(logonTokenDuplicate);

    Please correct your post or please tell me what you were doing.

    Thanks,
    Tom

  2. Hi Tom, thanks for the reply, in answer to your comments

    >>_wic = WindowsIdentity.Impersonate(IntPtr.Zero);
    The _wic member is defined as private WindowsImpersonationContext at the top of the Impersonator class.

    >>WindowsIdentity.Impersonate(IntPtr.Zero);
    This method call effectively calls the Win32 API RevertToSelf() function which under an impersonating context returns the thread identity to that of the IIS application pool, under which the LogonUser() method is called.

    >>Can’t use “var” in C#
    The var keyword is new in C# 3.0

    Cheers,
    Phil.

  3. I am newbie in that of coding C#.

    But this code could be help me in some case like that?.

    PC_OUT_OF_DOMAIN ——> DOMAIN ——> PC_RUNNING_SERVICES_IN_DOMAIN
    (this pc is by security,is a dmz)
    USER_X to access share folder USER_Y RUNNING APPLICATION.

    I want impersonate in PC_RUNNING_SERVICES_IN_DOMAIN with USERY ,but i want a network credential different
    of USERX this account only exists in PC_OUT_OF_DOMAIN .

    That is possible ?

    and i only need to implement a different identity?
    var wi = new WindowsIdentity(logonTokenDuplicate);
    wi.Impersonate(); // discard the returned identity context (which is the context of the application pool)

    I have two work around for my problem, but i am looking for a solution like the map network drive.
    Run with a local user(different) and is mapped with a remote user.

    Thanks in advance for all.

    Regards,
    ELC

  4. I totally love you.

    The seemingly minor detail about the type of logon for accessing network resources has been seriously bugging my ass for a while.
    Since everyone who has done a copy-from-msdn-and-presented-as-own-code has neglected to face/mention this fact, you finally saved me. Thanks a _lot_ !

    /J.

  5. Hi, really great article, but I have a problem.
    It throws an exception: “Logon failure: unknown user name or bad password” when it runs Win32NativeMethods.LogonUser(…).

    Context: I need to access a web application (IIS) within the intranet and not to insert crediantials manually. Username, domain and password I use are profiled on the remote machine, not on my local machine. Could this be the problem?

    Could you help me, please?

    Thanks

  6. Yes, they are correct.
    For your information, IIS uses Windows Authentication for my remote web application.
    I’m on a machine A and I want to access web-application on machine B using a domain account that has permissions on machine B, but I don’t want to type username and password manually.

  7. But since the exception is raised by “LogonUser()”, before setting the server’s address, it means that this solution needs the user be a local user on the client machine.

    Isn’t that right?

    Should I assume that there is no solution form my problem?

    Thanks again for your availability.

  8. Hi everyone,
    great and useful article.
    I have one question about using impersonation.
    I need it for connection to sql server.
    When I use LOGON32_LOGON_NEW_CREDENTIALS and connect to sql server running on remote machine: all is ok.
    But when I run my app on teh same machine where sql server works – i see that user remains the same, no impersonation…
    How to solve this problem?
    Thank you

  9. Thanks for the little tidbit about the LOGON_NETWORK_NEW_CREDENTIALS. I was having trouble accessing a network share using impersonation, but could map a drive to the share and access it fine. It drove me nuts for an hour or so until I stumbled across this. Thanks a lot!

  10. I seem to be having an issue with your code. First, I’m not using this for an ASP.NET website. What I have will be a batch process that will impersonate a domain user account to do some tasks including moving files from one UNC server path (\\localhost\test) to a physical folder (c:\test2).
    When I use LOGON32_LOGON_NEW_CREDENTIALS and LOGON32_PROVIDER_WINNT50 the Windows Identity still has the current identity as the currently logged in user.
    Secondly I cannot connect to the UNC \\localhost\test which has both full access NTFS and full access Share permissions for the Impersonated user. Well, I can connect but I cannot Delete or Create or Modify files/folders.
    Am I doing something wrong? – Chris

    using (var impersonator = new Impersonator(“testuser”, “MyDomain”,”MyTestPassword”, LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50))
    {
    var identity = WindowsIdentity.GetCurrent();
    }

  11. @Chris: The way I’m getting it to work is by providing an output pointer parameter in the LogonUser method, you then pass that pointer into the ImpersonateLoggedOnUser method. See the following example:

    IntPtr lnToken;

    int TResult = ImpersonationAPI.LogonUser(“UserName”, “Domain”, “Password”, ImpersonationAPI.LOGON32_LOGON_NEW_CREDENTIALS, ImpersonationAPI.LOGON32_PROVIDER_DEFAULT, out lnToken);

    if (TResult > 0)
    {
    ImpersonationAPI.ImpersonateLoggedOnUser(lnToken);
    }

    Note: ImpersonationAPI is my implementation of the Impersonator, which differs from this site’s. Mine is defined as follows:

    public class ImpersonationAPI
    {

    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;

    [DllImport(“advapi32.dll”, SetLastError=true)]
    public static extern int LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
    int dwLogonType, int dwLogonProvider, out IntPtr phToken);

    [DllImport(“advapi32.dll”, SetLastError=true)]
    public static extern int ImpersonateLoggedOnUser(IntPtr hToken);

    [DllImport(“advapi32.dll”, SetLastError=true)]
    public static extern int RevertToSelf();

    [DllImport(“kernel32.dll”, SetLastError=true)]
    public static extern int CloseHandle(IntPtr hObject);
    }

  12. Devin, I tried your implementation and it still didn’t work. I’m not sure if something has changed since this post or what. I did come across “WindowsImpersonationContext” (http://www.vanotegem.nl/PermaLink,guid,36633846-2eca-40fe-9957-2859d8a244dc.aspx) and tried that. Wierd thing is that it worked. I looked at differences and it looks like I need to use LOGON32_LOGON_INTERACTIVE and LOGON32_PROVIDER_DEFAULT to work. Any other combination does not seem to work on any capacity. This was tested on Windows 2003 x86 & x64 Servers. Once I changed your implementation and the this post’s implementation to use those parameters all works fine. (LOGON32_LOGON_INTERACTIVE & LOGON32_PROVIDER_DEFAULT)

  13. I just read the differences between LOGON32_LOGON_INTERACTIVE and LOGON32_LOGON_NEW_CREDENTIALS. I do not want “interactive”. Users are not going to be interactively logged in. So my last comment while making it work, is not the “ideal” solution.

    Would anyone have or know of the reasons why this would not work for LOGON32_LOGON_NEW_CREDENTIALS and LOGON32_PROVIDER_WINNT50?

    To me it seems like no impersonation is going on when using WINNT50 and NEW_CREDENTIALS.

  14. Ok more info. I have no idea why this is but this is what I’m finding out. I created a share on another server so not using \\localhost\test anymore. Same (I’ve checked 3 times now) Full Access Share Permissions and NTFS Permissions as the \\localhost\test. However after changing the path to a different server I’m able to use LOGON32_LOGON_NEW_CREDENTIALS. It seems I cannot use “new_credentials” on localhost unless there is some ACL that is screwed up. Hopefully someone finds this info useful and hopefully submits a reason. This has driven me nuts for the past 2 weeks.

  15. Hi,

    I Need some details on below point,

    _wic = WindowsIdentity.Impersonate(IntPtr.Zero);

    The above statement reverts the context from logon user to the app pool >> identity user. Thats ok, but are their any required permissions needed to execute this statement? means did above statement will fail in case the logon user did not have some permission?

    Thanks in advance!

  16. Throws the following error on Impersonate():

    “The security database on the server does not have a computer account for this workstation trust relationship”

    Specs:
    ASP .Net 4.0
    Server 2008 64bit
    Local Administrator account

    As a little background, domain credentials do not work on this machine for local logon – it returns the same error as above, however, using MS ldp.exe tool, I can bind to DC using credentials np.

    Trying to figure out how MS ldp tool is doing this, your blog is the closest I’ve found so far.

  17. Three years after you posted this and it’s still saving people from failure. Thanks for sharing your research and for keeping it available all this time.

  18. Kudos my friend =) very good aticle… got my app working with LOGON32_LOGON_NEW_CREDENTIALS as your logon type and LOGON32_PROVIDER_WINNT50 as the logon provider type.

  19. Hi, not sure whether you can see my post now. But I have to ask one question here. Basically. the code above works for me (I need to use LOGON32_LOGON_INTERACTIVE as logon type), at least I can use WindowsIdentity.GetCurrent().Name to check the current user has been updated to the one we want to change. But I don’t know why the following code still used my windows logon account to be opened after I change the account. Do you know why? Really appreciated if you can share anything. Thanks a lot^_^

    InternetExplorer ie = new InternetExplorer();
    IWebBrowserApp wb = (IWebBrowserApp)ie;

    wb.Visible = true;
    object o = null;
    wb.Navigate(mysharepointweburl, ref o, ref o, ref o, ref o);

    Additional info
    LogonType: LOGON32_LOGON_INTERACTIVE
    LogonProvider: LOGON32_PROVIDER_WINNT50

  20. works like charm, I had to call with LogonType.LOGON32_LOGON_NEW_CREDENTIALS & LogonProvider.LOGON32_PROVIDER_WINNT50 parameters.

    Thanks man

  21. Hi

    I’m a newbie to C#, and struggling with a small app that can stop and start a service on another machine on the network.

    The user needed is in some cases a domain user, in other a local user on the remote machine.

    This looks like really nice code, and I will see if it is suitable to my needs. I pretty sure it will work with a domain user, but not sure with a local user on the remote machine.

    Anyone having any experience with this code, in combination with a local user on the remote machine?

    Kind regards

    Morten, Denmark

  22. Hi Phil
    What combination of LogonType and LogonProvider shall be specified to impersonate local Administrator just in order to change date&time of local system? I am a newbie to C# and have tried almost all the combos with no success.
    Thanks
    Nadia

  23. Hi Phil!
    Great code, great effort!
    Kindly guide me about which combination of LogonType and LogonProvider values to choose in order to impersonate local Administrator, just to change date&time of local system? I am a newbie and have tried almost all the possible combos of these 2 with no success.
    Thanks in advance…
    Nadia

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.