How can I safely set the user principle in the HttpMessageHandler custom web application?

For basic authentication, I implemented a custom HttpMessageHandler based on the example shown in Darina Dimitrov, here: https://stackoverflow.com/a/464829/

The code creates an instance of principal type GenericPrincipal with username and roles, and then sets this principle to the current principle of the stream:

 Thread.CurrentPrincipal = principal; 

Later in the ApiController method ApiController principal can be read by accessing the properties of the User controllers:

 public class ValuesController : ApiController { public void Post(TestModel model) { var user = User; // this should be the principal set in the handler //... } } 

This seemed like normal until I recently added a custom MediaTypeFormatter which uses the Task library as follows:

 public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) { var task = Task.Factory.StartNew(() => { // some formatting happens and finally a TestModel is returned, // simulated here by just an empty model return (object)new TestModel(); }); return task; } 

(I have such an approach to start a task from Task.Factory.StartNew in ReadFromStreamAsync from some sample code. Is this wrong and maybe the only reason for the problem?)

Now "sometimes" - and for me it seems random - the User principle in the controller method is no longer the main one that I set in MessageHandler, that is, the username, Authenticated and roles are lost. The reason is that the custom MediaTypeFormatter causes a thread change between the MessageHandler and the controller method. I confirmed this by comparing the values ​​of Thread.CurrentThread.ManagedThreadId in the MessageHandler and in the controller method. "Sometimes," they are different, and then "lost."

Now I was looking for an alternative to setting Thread.CurrentPrincipal to somehow pass the principal safely from the custom MessageHandler to the controller method and to this blog post :

 request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(identity, new string[0])); 

I wanted to check this out, but it seems that the HttpPropertyKeys class (which is in the System.Web.Http.Hosting ) no longer has the UserPrincipalKey property in recent versions of WebApi (release candidate and final release from last week as well).

My question is: how can I change the last code snippet above to work with the current version of WebAPI? Or in general: how can I set a user principle in a custom MessageHandler and reliably get it in a controller method?

Edit

It is mentioned here that " HttpPropertyKeys.UserPrincipalKey ... resolves "MS_UserPrincipal" ", so I tried using:

 request.Properties.Add("MS_UserPrincipal", new GenericPrincipal(identity, new string[0])); 

But it does not work as I expected: the ApiController.User property ApiController.User not contain the principal added to the Properties collection above.

+47
security asp.net-mvc asp.net-web-api
Aug 19 '12 at 17:43
source share
3 answers

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

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

Important: setting up the client principal in the ASP.NET web interface

Due to some unsuccessful mechanisms deeply immersed in ASP.NET, installing Thread.CurrentPrincipal in web hosting is not enough.

When hosted in ASP.NET, Thread.CurrentPrincipal can be overridden with HttpContext.Current.User when creating new threads. This means you must install the principal both in the stream and in the HTTP context.

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

Today, the user will need to set both of the following rules: if you use a special message handler to perform authentication in the web host.

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

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

Edit

Just to emphasize this: Installing the current user principal HttpContext is only necessary if WebApi is hosted in ASP.NET/IIS. For self-hosting, this is optional (and not possible, because HttpContext is an ASP.NET construct and does not exist in self-service).

+72
Aug 19 '12 at 23:25
source

To avoid context switching, try using 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; } 
+5
Aug 19 '12 at 20:22
source

Using your own MessageHandler, you can 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); } 

Please note that this adds this property to the request properties collection; it does not change the user connected to ApiController.

+4
Sep 14
source



All Articles