10

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?

Jon-Eric
  • 16,977
  • 9
  • 65
  • 97
  • 1
    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. – Matthew May 14 '13 at 20:52
  • @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. – Jon-Eric May 14 '13 at 20:56
  • From this stackoverflow answer, it appears if you set CurrentPrincipal it will persist into the calling thread: http://stackoverflow.com/a/12460170/507793, otherwise I'm not sure how to solve your problem. – Matthew May 14 '13 at 21:00
  • possible duplicate of [Using ASP.NET Web API, my ExecutionContext isn't flowing in async actions](http://stackoverflow.com/questions/15964244/using-asp-net-web-api-my-executioncontext-isnt-flowing-in-async-actions) – Stephen Cleary May 14 '13 at 21:03

1 Answers1

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.

Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 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. – Jon-Eric May 14 '13 at 22:06
  • 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? – Stephen Cleary May 14 '13 at 22:55
  • Setting `UseTaskFriendlySynchronizationContext` to true fixed the problem! Thank you for taking the time to investigate! – Jon-Eric May 15 '13 at 15:29
  • 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! – Stephen Cleary May 15 '13 at 17:06
  • Stephen, after further investigation, it appears that my example only works because `await Task.Delay(1000)` is present. I asked [a new question](http://stackoverflow.com/q/16653308) about this specifically. If you have time, I'd appreciate any input you might have. – Jon-Eric May 20 '13 at 15:53