0

I have a .NET 6 web application that needs to impersonate the current domain user to perform some operations on their behalf.
Users authenticate through OAuth, so all I have is a ClaimsIdentity from which I can extract the user's UPN.

I have a working solution with .NET Framework 4.8. My problem is that I cannot find a way to do the same with .NET 6.

Below are all the details of the setup and what I tried already.

Working solution with .NET Framework 4.8

On the web server I have configured the Claims to Windows Token service (c2wts) to allow the application pool identity to impersonate users.
I have also granted to the application pool identity the following privileges through the local policy editor:

  • Act as part of the operating system
  • Allow log on locally
  • Generate security audits
  • Impersonate a client after authentication
  • Log on as a service

With this setup, using .NET Framework 4.8 I could impersonate the current user with something like this:

void WorksWithFramework_4_8(string userUpn) {
    var windowsIdentity = S4UClient.UpnLogon(userUpn);
    WindowsImpersonationContext context = windowsIdentity.Impersonate();
    // Do something...
    context.Undo();
}

.NET 6 attempt 1: S4UClient.UpnLogon

If I try to call S4UClient.UpnLogon, I get this exception:

The type initializer for 'Microsoft.IdentityModel.WindowsTokenService.S4UClient' threw an exception.
Inner Exception: FileNotFoundException: Could not load file or assembly 'System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. The system cannot find the file specified.

Things I tried that did not work:

  • Adding a reference to the package System.ServiceModel.Primitives as suggested in this answer
  • Uninstalling and reinstalling the framework as suggested in this answer

.NET 6 attempt 2: WindowsIdentity.RunImpersonated

static void RunImpersonatedTest(string upn) { 
    WindowsIdentity identity = new WindowsIdentity(upn);
    WindowsIdentity.RunImpersonated(identity.AccessToken, () => {

        // This prints the expected output
        Console.WriteLine(WindowsIdentity.GetCurrent().Name);

        // If I try to access something that actually requires privileges, nothing works...
        foreach (var file in Directory.EnumerateFiles(@"\\SOME-MACHINE\C$\tmp")) {
            Console.WriteLine(file);
        }
    });
}

If I use WindowsIdentity.RunImpersonated, as soon as I try to do anything I get an access denied error, or this exception:

Either a required impersonation level was not provided, or the provided impersonation level is invalid. (Exception from HRESULT: 0x80070542)

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
  • [By default impersonation doesn't work](https://devblogs.microsoft.com/oldnewthing/20110928-00/?p=9533), since everything that wants to support impersonation needs to do so explicitly throughout the whole stack. This makes it very easy to unintentionally break, unfortunately, so if you have any opportunity to move away from impersonation it would be good to take it. Upgrading to a brand new version of .NET is as good an opportunity as any -- alternatively, it's a good argument for remaining with your current version, or at least for the bits that require impersonation. – Jeroen Mostert Jun 20 '23 at 10:04
  • The same operations, on the same machine and running with the same credentials, work on the 4.8 version. If I could avoid impersonation I would, but sadly I cannot. It's still astonishing that there is no way to do this in .NET 6. – Paolo Tedesco Jun 20 '23 at 10:14
  • 2
    Not so astonishing if you consider that impersonation is a Windows-only thing when .NET Core is very much about multi-platform, combined with the fact that few greenfield projects would need impersonation. I'm not saying it can't be made to work, mind you, just that it's not very surprising that working out all the kinks isn't necessarily high on the priority list. – Jeroen Mostert Jun 20 '23 at 10:17
  • FYI `Directory.EnumerateFiles(@"\\SOME-MACHINE\C$\tmp")` is not a call for impersonation, it is a call for delegation. Which will require multihop and Kerberos... – Aron Jun 20 '23 at 14:09
  • Thanks Aron. Bad example most likely, but with the same calls as in the .NET 4.8 version and same account constrained delegation is configured correctly, and things still don't work. I'm going to create a separate service running on .NET framework in the end. – Paolo Tedesco Jun 21 '23 at 10:22

0 Answers0