Set Thread.CurrentPrincipal Asynchronously?
Asked Answered
S

1

11

Using ASP.NET WebAPI, during authentication, Thread.CurrentPrincipal is set so that controllers can later use the ApiController.User property.

If that authentication step becomes asynchronous (to consult another system), any mutation of CurrentPrincipal is lost (when the caller's await restores the synchronization context).

Here's a very simplified example (in the real code, authentication happens in an action filter):

using System.Diagnostics;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;

public class ExampleAsyncController : System.Web.Http.ApiController
{
    public async Task GetAsync()
    {
        await AuthenticateAsync();

        // The await above saved/restored the current synchronization
        // context, thus undoing the assignment in AuthenticateAsync(). 
        Debug.Assert(User is GenericPrincipal);
    }

    private static async Task AuthenticateAsync()
    {
        // Save the current HttpContext because it's null after await.
        var currentHttpContext = System.Web.HttpContext.Current;

        // Asynchronously determine identity.
        await Task.Delay(1000);
        var identity = new GenericIdentity("<name>");

        var roles = new string[] { };
        Thread.CurrentPrincipal = new GenericPrincipal(identity, roles);
        currentHttpContext.User = Thread.CurrentPrincipal;
    }
}

How do you set Thread.CurrentPrincipal in an async function such that the caller's await doesn't discard that mutation when restoring the synchronization context?

Strangury answered 14/5, 2013 at 20:47 Comment(4)
I'm not sure about your problem, but ideally it would be best to authenticate them before the controller is invoked. In my WebAPI projects, I like to setup a DelegatingHandler which does authentication before it gets to the controller.Mellissamellitz
@Matthew, I agree and that's how the real code works, an action filter handles the authentication before the controller method is invoked. I excluded the action filter to simplify my example, but the problem is the same in either situation.Strangury
From this stackoverflow answer, it appears if you set CurrentPrincipal it will persist into the calling thread: https://mcmap.net/q/95317/-understanding-context-in-c-5-async-await, otherwise I'm not sure how to solve your problem.Mellissamellitz
possible duplicate of Using ASP.NET Web API, my ExecutionContext isn't flowing in async actionsShermanshermie
S
11

You have to set HttpContext.Current.User as well. See this answer and this blog post for more info.

Update: Also ensure you are running on .NET 4.5 and have UserTaskFriendlySynchronizationContext set to true.

Shermanshermie answered 14/5, 2013 at 21:4 Comment(5)
Thank you for you help Stephen. I added setting of HttpContext.Current.User (and updated the code in my question), but it doesn't help. The problem is unchanged.Strangury
It is definitely working for me. I create a new WebAPI project with the code above in the ValuesController, and it works just fine. Follow-up questions: are you running on .NET 4.5, and do you have UseTaskFriendlySynchronizationContext set to true?Shermanshermie
Setting UseTaskFriendlySynchronizationContext to true fixed the problem! Thank you for taking the time to investigate!Strangury
You're welcome! I edited the answer to be more complete. Odd thing is, on my machine it works whether UTFSC is set to true or false. I'm not sure of the exact conditions where UTFSC would make a difference, but I'm glad it helped!Shermanshermie
Stephen, after further investigation, it appears that my example only works because await Task.Delay(1000) is present. I asked a new question about this specifically. If you have time, I'd appreciate any input you might have.Strangury

© 2022 - 2024 — McMap. All rights reserved.