How do you do Impersonation in .NET?

“Impersonation” in the .NET space generally means running code under a specific user account. It is a somewhat separate concept than getting access to that user account via a username and password, although these two ideas pair together frequently.

Impersonation

The APIs for impersonation are provided in .NET via the System.Security.Principal namespace:

  • Newer code should generally use WindowsIdentity.RunImpersonated, which accepts a handle to the token of the user account, and then either an Action or Func<T> for the code to execute.

    WindowsIdentity.RunImpersonated(userHandle, () =>
    {
        // do whatever you want as this user.
    });
    

    or

    var result = WindowsIdentity.RunImpersonated(userHandle, () =>
    {
        // do whatever you want as this user.
        return result;
    });
    

    There’s also WindowsIdentity.RunImpersonatedAsync for async tasks, available on .NET 5+, or older versions if you pull in the System.Security.Principal.Windows Nuget package.

    await WindowsIdentity.RunImpersonatedAsync(userHandle, async () =>
    {
        // do whatever you want as this user.
    });
    

    or

    var result = await WindowsIdentity.RunImpersonated(userHandle, async () =>
    {
        // do whatever you want as this user.
        return result;
    });
    
  • Older code used the WindowsIdentity.Impersonate method to retrieve a WindowsImpersonationContext object. This object implements IDisposable, so generally should be called from a using block.

    using (WindowsImpersonationContext context = WindowsIdentity.Impersonate(userHandle))
    {
        // do whatever you want as this user.
    }
    

    While this API still exists in .NET Framework, it should generally be avoided.

Accessing the User Account

The API for using a username and password to gain access to a user account in Windows is LogonUser – which is a Win32 native API. There is not currently a built-in managed .NET API for calling it.

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);

This is the basic call definition, however there is a lot more to consider to actually using it in production:

  • Obtaining a handle with the “safe” access pattern.
  • Closing the native handles appropriately
  • Code access security (CAS) trust levels (in .NET Framework only)
  • Passing SecureString when you can collect one safely via user keystrokes.

Instead of writing that code yourself, consider using my SimpleImpersonation library, which provides a managed wrapper around the LogonUser API to get a user handle:

using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
using SimpleImpersonation;

var credentials = new UserCredentials(domain, username, password);
using SafeAccessTokenHandle userHandle = credentials.LogonUser(LogonType.Interactive);  // or another LogonType

You can now use that userHandle with any of the methods mentioned in the first section above. This is the preferred API as of version 4.0.0 of the SimpleImpersonation library. See the project readme for more details.

Remote Computer Access

It’s important to recognize that impersonation is a local machine concept. One cannot impersonate using a user that is only known to a remote machine. If you want to access resources on a remote computer, the local machine and the remote machine must be attached to the same domain, or there needs to be a trust relationship between the domains of the two machines. If either computer is domainless, you cannot use LogonUser or SimpleImpersonation to connect to that machine.

Leave a Comment