masks

Programmatic impersonation in C#

Impersonation

I recently deployed a WPF app on a server that allowed the user to stop and start some application-related services. The purpose of the app was to allow users with administrative rights an easy way to manage the services that they needed to manage. Granted, they could manage the services through the services MMC, but the little WPF app was a requirement, and it’s our job as developers to make things easier for our clients – right?

All went well until a change of requirements meant that a user without administrative rights needed to use the program to stop and start the required services. When I tried to use the app, I got an exception – quite rightly, stopping and starting the services required admin rights. We needed the restricted user to be able to log on and use the app, but still needed to restrict their permissions.

So – step in programmatic impersonation in C# – a way to give restricted users the power that that’s required, all within the confines of your application.

The first thing to point out is that I got quite a bit of this code from a google search, but I had to do a bit of work to get things in a state that I found really useful.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Runtime.InteropServices;
using System.Linq;
using System.Security.Principal;
using System.Text;
namespace ServiceControllerApp.Security
{
    public class Impersonator : IDisposable
    {
        private WindowsImpersonationContext _impersonatedUser = null;
        private IntPtr _userHandle;
        public Impersonator()
        {
            _userHandle = new IntPtr(0);
            string user = "servicecontroller";
            string userDomain = ConfigurationManager.AppSettings["MachineDomain"];
            string password = "yourpassword";
            bool returnValue = LogonUser(user, userDomain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref _userHandle);
            if (!returnValue)
                throw new ApplicationException("Could not impersonate user");
            WindowsIdentity newId = new WindowsIdentity(_userHandle);
            _impersonatedUser = newId.Impersonate();
        }
        #region IDisposable Members
        public void Dispose()
        {
            if (_impersonatedUser != null)
            {
                _impersonatedUser.Undo();
                CloseHandle(_userHandle);
            }
        }
        #endregion
        #region Interop imports/constants
        public const int LOGON32_LOGON_INTERACTIVE = 2;
        public const int LOGON32_LOGON_SERVICE = 3;
        public const int LOGON32_PROVIDER_DEFAULT = 0;
        [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
        public static extern bool LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public extern static bool CloseHandle(IntPtr handle);
        #endregion
    }
}

Impersonator is a simple class that uses interop to call Win32 LogonUser and CloseHandle functions. We have to use interop because .NET doesn’t provide the equivalent methods.

The code shown above has a user, domain and password actually in the code – for some situations this is a security risk, so the credentials should be obtained in another manner, but for my needs, it was satisfactory, and their direct inclusion simplifies this example.

The class has a WindowsImpersonationContext to manage the impersonation, and the constructor sets up the required logon rights using the LogonUser interop.

Crucially, the impersonation must end, with an equivalent Log Off – and the class implements IDisposable to call the required log off code. Using the class is easy.

using (Impersonator impersonator = new Impersonator())
{
    // code in here
}

The good thing is that because the class implements IDisposable, you don’t have to pepper your code with the log off code equivalent. I hope it’s of use to somebody wishing to implement impersonation.

  • Tobias

    Nice class. More compact and usable than the mdsn examples. Workes fine.

    Thanks!

  • peter

    very nice class…

    just modified the constructor to accept domain username and password as parameters and i was good to go :)

  • TomV

    Very good example. Straight forward and well documented!

    Also changed constructor to accept sensitive data.

  • http://www.geeks.ltd.uk/Services.html software development uk

    Humm… interesting,

    I have read the exact thing on MSDN but you wrote it much better than they did

    Thanks for writing, most people don’t bother.

  • http://www.phillduffy.com Phill

    Thank you for taking the time to write this succinct, clear and importantly – working code.

  • Sam

    Thanks, this works perfectly.

    But is there any way to impersonate a user from a different domain?

  • Lardus

    Thanks a lot! This worked perfectly on my application where I needed to access a DB Warehouse with windows authentication but was logged in with a local account!

  • Jeremy

    This little class works brilliantly. Thank you very much!