How can I safely set the user principal in a custom WebAPI HttpMessageHandler?
Solution 1
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).
Solution 2
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;
}
Solution 3
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.
Slauma
Updated on July 09, 2022Comments
-
Slauma almost 2 years
For basic authentication I have implemented a custom
HttpMessageHandler
based on the example shown in Darin Dimitrov's answer here: https://stackoverflow.com/a/11536349/270591The code creates an instance
principal
of typeGenericPrincipal
with user name and roles and then sets this principal to the current principal of the thread:Thread.CurrentPrincipal = principal;
Later in a
ApiController
method the principal can be read by accessing the controllersUser
property:public class ValuesController : ApiController { public void Post(TestModel model) { var user = User; // this should be the principal set in the handler //... } }
This seemed to work fine until I recently added a custom
MediaTypeFormatter
that uses theTask
library like so: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 this approach to start a task with
Task.Factory.StartNew
inReadFromStreamAsync
from some sample code. Is it wrong and maybe the only reason for the problem?)Now, "sometimes" - and for me it appears to be random - the
User
principal in the controller method isn't the principal anymore I've set in the MessageHandler, i.e. user name,Authenticated
flag and roles are all lost. The reason seems to be that the custom MediaTypeFormatter causes a change of the thread between MessageHandler and controller method. I've confirmed this by comparing the values ofThread.CurrentThread.ManagedThreadId
in the MessageHandler and in the controller method. "Sometimes" they are different and then the principal is "lost".I've looked now for an alternative to setting
Thread.CurrentPrincipal
to somehow transfer the principal safely from the custom MessageHandler to the controller method and in this blog post request properties are used:request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(identity, new string[0]));
I wanted to test that but it seems that the
HttpPropertyKeys
class (which is in namespaceSystem.Web.Http.Hosting
) doesn't have aUserPrincipalKey
property anymore in the recent WebApi versions (release candidate and final release from last week as well).My question is: How can I change the last code snippet above so that is works with the current WebAPI version? Or generally: How can I set the user principal in a custom MessageHandler and access it reliably in a controller method?
Edit
It is mentioned here that "
HttpPropertyKeys.UserPrincipalKey
... resolves to“MS_UserPrincipal”
", so I tried to use:request.Properties.Add("MS_UserPrincipal", new GenericPrincipal(identity, new string[0]));
But it doesn't work as I expected: The
ApiController.User
property does not contain the principal added to theProperties
collection above. -
Slauma over 11 yearsIt works, but I'm a bit worried to have to be that careful with the usage of tasks and threads to keep a security feature intact. I've found another solution, see my own answer here.
-
Muhammad Adeel Zahid over 11 years@Darin Dimitrov, I have installed the RTM of vs 2012 and latest bits of asp.net web api and HttpPropertyKeys.UserPrincipalKey gives syntax error. viewing class in object browser shows that this key is not present in final release. Any ideas what else can i use?
-
Darin Dimitrov over 11 yearsWhy do you want to use
HttpPropertyKeys.UserPrincipalKey
? Where in my answer did you see using it? So don't use it. -
G. Stoynev over 10 years@DarinDimitrov, doesn't doing this defeat explicit threading of the Async? I.e. original code runs the task on a pool thread. Isn't there a different approach to preserve the context?