Search code examples
.netasp.net-web-apibasic-authentication

WebApi: User.Identity.IsAuthenticated true for first request, false thereafter


I've implemented a Web API.

<authentication mode="None" />

I'm using Basic authorization, and set the Thread.CurrentPrincipal in my AuthorizeAttribute.

The first time after starting/debugging the application, I submit a request, set the Thread.CurrentPrincipal (with IsAuthenticated = true) server-side, and IsAuthenticated returns true in my controller, as expected. Any request after this, however, sets the Thread.CurrentPrincipal as normal, but by time the execution hits my controller methods, the controllers' User.Identity property has been changed, and IsAuthenticated = false.

I can't figure out why IsAuthenticated=true for the first time after starting the application only?! It should be every time, as I'm setting the Thread.CurrentPrinciple manually, but somewhere between there and hitting my controller, it is being replaced!

UPDATE

It's something to do with a MediaTypeFormatter that I've added. When I remove the formatter, I don't get the issue. The formatter's code that gets executed is below:

public override Task<object> ReadFromStreamAsync(Type type, System.IO.Stream webStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
    {
        return Task.Factory.StartNew(() =>
        {
            string temporaryFilePath = Path.Combine(TemporaryDirectory, Path.GetRandomFileName());

            using (FileStream fileStream = new FileStream(temporaryFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read))
            {
                webStream.CopyTo(fileStream);
            }

            return (object)new CustomFile(temporaryFilePath);
        });
    }

Solution

  • The answer is explained in some detail here.

    In short, setting the Thread.CurrentPrincipal is not enough. I have set the HttpContext.Current.User too now, and it is working.

    In the original post, the async method on the MediaTypeFormatter was being called, creating additional threads, resulting in the CurrentPrinciple being attached to some other thread, and not the one that your controller action ends up being executed on.

    As for why it has to be set in two places, the explanation can be found here. It says:

    If your application performs any custom authentication logic, you must set the principal on two places:

    Thread.CurrentPrincipal: This property is the standard way to set the thread's principal in .NET. HttpContext.Current.User: This property is specific to ASP.NET.

    The following code shows how to set the principal:

    private void SetPrincipal(IPrincipal principal)
    {
        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
        {
            HttpContext.Current.User = principal;
        }
    }
    

    For web-hosting, you must set the principal in both places; otherwise the security context may become inconsistent. For self-hosting, however, HttpContext.Current is null. To ensure your code is host-agnostic, therefore, check for null before assigning to HttpContext.Current, as shown.