I have a situation where a (legacy) .Net Framework Web API needs to make an impersonated call into a separately-maintained .Net library. The call will perform a long running (~1 sec) operation, so it provides a method returning a Task
for this purpose. The library itself is not aware that there is impersonation involved, as the decision to impersonate is made by the Web API.
The issue is that inside the library, the impersonation context gets lost when a call to Task.Run
is made. In the sample output below, I would expect that the "Inside Task.Run..." line to report DOMAIN\userone as the current user.
The code below represents a minimal reproduction of the issue. For simplicity, a simulation of the long-running call has been inlined as taskFromWorkerLibrary
. Interestingly, this only seems to happen with legacy ASP.NET. Creating an Asp.Net Core Web API with the same example code is able to persist the impersonation context through the Task.Run
correctly.
public class ValuesController : ApiController
{
// GET api/values
public async Task<IEnumerable<string>> Get()
{
List<string> logs = new List<string>();
logs.Add($"Before impersonation: {WindowsIdentity.GetCurrent().Name} on thread {Thread.CurrentThread.ManagedThreadId}");
IntPtr logonToken = ((WindowsIdentity)User.Identity).Token;
int result = await WindowsIdentity.RunImpersonated(new Microsoft.Win32.SafeHandles.SafeAccessTokenHandle(logonToken), async () =>
{
logs.Add($"During impersonation: {WindowsIdentity.GetCurrent().Name} on thread {Thread.CurrentThread.ManagedThreadId}");
Task<int> taskFromWorkerLibrary = Task.Run(() =>
{
logs.Add($"Inside Task.Run: {WindowsIdentity.GetCurrent().Name} on thread {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(3000); // Simulate long-running operation
return new Random().Next(); // Just so we can tell the difference between calls
});
return await taskFromWorkerLibrary;
});
logs.Add($"Result: {result} on thread {Thread.CurrentThread.ManagedThreadId}");
logs.Add($"After impersonation: {WindowsIdentity.GetCurrent().Name} on thread {Thread.CurrentThread.ManagedThreadId}");
return logs.ToArray();
}
}
Output:
Before impersonation: DOMAIN\app_pool on thread 33
During impersonation: DOMAIN\userone on thread 33
Inside Task.Run: DOMAIN\app_pool on thread 31
Result: 1625421834 on thread 35
After impersonation: DOMAIN\app_pool on thread 35