How can I safely set the user principal in a custom WebAPI HttpMessageHandler?

The problem of losing the principal on a new thread is mentioned here:

http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/

Important: Setting the Client Principal in ASP.NET Web API

Due to some unfortunate mechanisms buried deep in ASP.NET, setting Thread.CurrentPrincipal in Web API web hosting is not enough.

When hosting in ASP.NET, Thread.CurrentPrincipal might get overridden with HttpContext.Current.User when creating new threads. This means you have to set the principal on both the thread and the HTTP context.

And here: http://aspnetwebstack.codeplex.com/workitem/264

Today, you will need to set both of the following for user principal if you use a custom message handler to perform authentication in the web hosted scenario.

IPrincipal principal = new GenericPrincipal(
    new GenericIdentity("myuser"), new string[] { "myrole" });
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;

I have added the last line HttpContext.Current.User = principal (needs using System.Web;) to the message handler and the User property in the ApiController does always have the correct principal now, even if the thread has changed due to the task in the MediaTypeFormatter.

Edit

Just to emphasize it: Setting the current user's principal of the HttpContext is only necessary when the WebApi is hosted in ASP.NET/IIS. For self-hosting it is not necessary (and not possible because HttpContext is an ASP.NET construct and doesn't exist when self hosted).


To avoid the context switch try using a TaskCompletionSource<object> instead of manually starting another task in your custom MediaTypeFormatter:

public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
    var tcs = new TaskCompletionSource<object>();

    // some formatting happens and finally a TestModel is returned,
    // simulated here by just an empty model
    var testModel = new TestModel();

    tcs.SetResult(testModel);
    return tcs.Task;
}

Using your custom MessageHandler you could add the MS_UserPrincipal property by calling the HttpRequestMessageExtensionMethods.SetUserPrincipal extension method defined in System.ServiceModel.Channels:

protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    var user = new GenericPrincipal(new GenericIdentity("UserID"), null);
    request.SetUserPrincipal(user);
    return base.SendAsync(request, cancellationToken);
}

Note that this only adds this property to the Request's Properties collection, it doesn't change the User attached to the ApiController.