13

I am self hosting WebApi with the following configuration:

Visual Studio 2012 / .NET 4.0

public void Configuration(IAppBuilder appBuilder)
{
    var config = new HttpConfiguration();

    // authentication
    config.MessageHandlers.Add(new Shield.PresharedKeyAuthorizer());

    // routing
    config.Routes.MapHttpRoute(
        name: "Default",
        routeTemplate: "{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    appBuilder.UseWebApi(config);
}

I have a simple test setup with the following DelegatingHandler to create a claim and attach it to the current thread.

public class PresharedKeyAuthorizer : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        var claims = new List<Claim>();
        claims.Add(new Claim(ClaimTypes.Name, "superstar"));

        var identity = new ClaimsIdentity(claims, "PresharedKey");
        var principal = new ClaimsPrincipal(identity);

        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
            HttpContext.Current.User = principal;

        return base.SendAsync(request, cancellationToken);
    }
}

However, when I hit the ApiController that is marked with the Authorize attribute, it doesn't recognize the authentication.

[Authorize]
public class FilesController : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "Secure File A", "Secure File B" };
    }
}

Removing the Authorize attribute and setting a breakpoint, I can see that RequestContext.Principal property is indeed null. The request works fin without the Authorize attribute, so I know the setup of the self hosting is correct, but I must be missing something in the authentication pipeline.

What am I missing to allow that claim to work against the Authorize attribute?

This related answer with the same approach appears to work when hosted by IIS: https://stackoverflow.com/a/14872968/118224

Community
  • 1
  • 1
Charlie Brown
  • 2,817
  • 2
  • 20
  • 31

1 Answers1

19

In the message handler, set the principal like this.

request.GetRequestContext().Principal = principal;

Do not use

Thread.CurrentPrincipal = principal;

if (HttpContext.Current != null)
    HttpContext.Current.User = principal;

UPDATE

It has been a while since I worked on .NET 4.0/2012/Web API <2. So, I cannot answer for sure. But with OWIN hosting, principal must be set in the OWIN context. OwinHttpRequestContext sets both Thread.CurrentPrincipal and the principal in OWIN context. By using request.GetRequestContext().Principal, these details are hidden from you. To make long story short, I believe if you some how set the principal in OWIN context, this will work. Not sure how you can do that from web API message handler. You can do that from OWIN middleware.

public void Configuration(IAppBuilder app)
{
    var config = new HttpConfiguration();
    config.Routes.MapHttpRoute("default", "api/{controller}/{id}");

    //config.MessageHandlers.Add(new PresharedKeyAuthorizer());

    app.Use((IOwinContext context, Func<Task> next) =>
    {
        var claims = new List<Claim>();
        claims.Add(new Claim(ClaimTypes.Name, "superstar"));

        var identity = new ClaimsIdentity(claims, "PresharedKey");
        var principal = new ClaimsPrincipal(identity);

        context.Request.User = principal;
        return next.Invoke();
    });

    app.UseWebApi(config);
}
  • Is this only available in WebApi 2.0? I am running .Net4.0/VS2012, which I will add to my answer. – Charlie Brown Jan 29 '14 at 13:43
  • Yes, but aren't you already using web API 2? I thought you are checking `Principal` on `HttpRequestContext`. – Badrinarayanan Lakshmiraghavan Jan 29 '14 at 16:15
  • Your correct, I am using webapi2. I'm actually using the AuthorizeAttribute on the Controller, which should be checking the Current thread principal I believe. – Charlie Brown Jan 29 '14 at 18:43
  • `[Authorize]` gets principal like this - `IPrincipal user = actionContext.ControllerContext.RequestContext.Principal;`. – Badrinarayanan Lakshmiraghavan Jan 30 '14 at 01:52
  • Thanks your first answer is correctly working now. It took me some time realize the `[Authorize]` attribute coming from WebApi has a different implementation than `[Authorize]` coming from System.Web. I just needed to reference the correct one. For portability, I am going to set all three values so it works regardless of where it gets hosted. – Charlie Brown Jan 30 '14 at 11:55
  • Gald it works. You don't need to set it to accommodate all types of hosting. `request.GetRequestContext().Principal = principal;` already does that for you. – Badrinarayanan Lakshmiraghavan Jan 30 '14 at 17:42
  • Thanks! Allowed me to avoid making a custom authorize attribute. – Kijana Woodard Oct 04 '15 at 18:11
  • @Badri In newer versions it uses `Thread.CurrentPrincipal` as `actionContext.ControllerContext.RequestContext` is no longer available in later versions of WebApi. – Casey Apr 22 '16 at 15:06
  • Thanks! the code that you provide in "UPDATE" works for me. With this I was able to configure self-host webapi for development purpose with some always logged in used for testing purposes – Mariusz Pawelski Jul 19 '19 at 08:28