24

I am trying to use the ASP.NET Web API Self-Host option with Windows authentication so I can determine the logged on user and ultimately accept or reject the user based on their identity. Here is my console application code:

using System;
using System.Web.Http;
using System.Web.Http.SelfHost;

namespace SelfHost
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new HttpSelfHostConfiguration("http://myComputerName:8080");
            config.UseWindowsAuthentication = true;

            config.Routes.MapHttpRoute(
                "API Default", "api/{controller}/{id}",
                new { id = RouteParameter.Optional });

            using (HttpSelfHostServer server = new HttpSelfHostServer(config))
            {
                server.OpenAsync().Wait();

                Console.WriteLine("Press Enter to quit.");
                Console.ReadLine();
            }
        }
    }
}

Here is the controller:

[Authorize]
public class HelloController : ApiController
{
    public string Get()
    {
        // This next line throws an null reference exception if the Authorize
        // attribute is commented out.
        string userName = Request.GetUserPrincipal().Identity.Name;
        return "Hello " + userName;
    }
}

Edit - I added the Authorize attribute, and the debugger shows that the code inside the Get action method is never invoked. The following HTML is returned:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META content="text/html; charset=windows-1252" http-equiv=Content-Type></HEAD>
<BODY></BODY></HTML>

If the Authorize attribute is commented out, Request.GetUserPrincipal().Identity.Name throws a null reference exception since Request.GetUserPrincipal() yields null.

Dave Johnson
  • 782
  • 1
  • 7
  • 17
  • 2
    You just need to put a break point and find out which property above is null... is "ControllerContext" null? or "Request" or "GetUserPrincipal()" or "Identity"? – Timothy Khouri Mar 05 '12 at 19:28
  • Yeah, what property is generating the null ref? – marcind Mar 06 '12 at 04:49
  • @marcind the `Request.GetUserPrincipal()` is null. I added the `[Authorize]` attribute as suggested by Eric King and then I just receive a bare bones HTML page with no content between two body tags and it never runs the code inside my `Get` action method in the controller class. – Dave Johnson Mar 06 '12 at 17:41
  • 1
    @jonnii thanks for adding the bounty! I have not solved this one. – Dave Johnson Mar 20 '12 at 14:49
  • I only need selfhost for integration testing, so I've hacked around it by using basic authentication specifically for that purpose (everything works in IIS). – jonnii Mar 20 '12 at 14:57
  • Hi, any update on this? I have same use case for my application. Is this fixed?thank you. – Dreamer May 22 '14 at 08:30
  • cool, just setting HttpSelfHostConfiguration.ClientCredentialType to windows is working - probably issue got fixed. – Dreamer May 22 '14 at 19:03

9 Answers9

25

I've hit this issue as well and the only solution I've came up with is to deliver dedicated HttpSelfHostedConfiguration:

public class NtlmSelfHostConfiguration : HttpSelfHostConfiguration
{
    public NtlmSelfHostConfiguration(string baseAddress)
        : base(baseAddress)
    { }

    public NtlmSelfHostConfiguration(Uri baseAddress)
        : base(baseAddress)
    { }

    protected override BindingParameterCollection OnConfigureBinding(HttpBinding httpBinding)
    {
        httpBinding.Security.Mode = HttpBindingSecurityMode.TransportCredentialOnly;
        httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
        return base.OnConfigureBinding(httpBinding);
    }
}

To use it you just need to change one line (you don't need to set UseWindowsAuthentication anymore):

var config = new NtlmSelfHostConfiguration("http://myComputerName:8080");

The only issue with this approach is that authentication is now required for every request made to server which is using this configuration.

tpeczek
  • 23,867
  • 3
  • 74
  • 77
  • Maybe you have a problem on caller side, how are you calling your host? I was using this against web application so the browser was handling the authentication headers on client-side. If you are using your own client you need to set proper authentication headers by your own (if you don't you *should* get unauthorized response). Please try to call your URL in browser to see if host is forcing the authentication. – tpeczek Mar 26 '12 at 14:31
  • I forgot to change the authentication scheme from basic => windows. My bad! This is a good work around for now, but as you said it means all the actions will require authentication. – jonnii Mar 26 '12 at 15:34
  • Also, I'm going to award you the bounty because this is probably the only work around to this. – jonnii Mar 26 '12 at 15:35
  • 1
    I do hope that this area is subject to change in actual release. We must remember that Web API is in beta status and that it has just taken a huge architecture change by moving from WCF to ASP.NET (this is most probably why there are so many issue in self-hosting, the team has focused on web-hosting scenario I believe). – tpeczek Mar 26 '12 at 15:43
  • 1
    I implemented this workaround solution and it works great for my use case since I am using Internet Explorer as my client. Many thanks tpeczek for the solution and @jonnii for generously providing the bounty to get this question more attention! I'm accepting this as the answer. – Dave Johnson Mar 26 '12 at 18:52
  • Hi, I have same use case. Is this fixed now? Thank you! – Dreamer May 22 '14 at 08:32
  • cool, just setting HttpSelfHostConfiguration.ClientCredentialType to windows is working - probably issue got fixed. – Dreamer May 22 '14 at 19:04
  • When doing HTTPS (this.BaseAddress.Scheme == Uri.UriSchemeHttps), you must set httpBinding.Security.Transport.ClientCredentialType after the call to base.OnConfigureBinding() and you must not set httpBinding.Security.Mode. – mheyman Dec 12 '14 at 23:39
  • Hi! Please take a look at my question http://stackoverflow.com/questions/35751411/authentication-in-self-host-web-api – NoWar Mar 02 '16 at 19:42
3

i have hosted "Web API" in windows service and this is what i did to support windows authentication (basically based on above question, answers, some related articles - i am just consolidating as it may be helpful for others)

@HTTP Server (web api):

Set (reference: http://msdn.microsoft.com/en-us/library/system.web.http.selfhost.httpselfhostconfiguration.clientcredentialtype(v=vs.118).aspx),

HttpSelfHostConfiguration.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Windows;

@Client:

And then as Allan mentioned (above) set UseDefaultCredentials to true.

Using HttpClient:

var handler = new HttpClientHandler();
    handler.UseDefaultCredentials = true;
    _httpClient = new HttpClient(handler);

Using WebClient (reference: http://msdn.microsoft.com/en-us/library/system.net.webclient.usedefaultcredentials.aspx )

set webclient's usedefaultcrednetials to 'true'.

Best Regards!

Dreamer
  • 3,371
  • 2
  • 34
  • 50
  • Hey , pls take a look at my question http://stackoverflow.com/questions/9571445/asp-net-web-api-self-host-with-windows-authentication – NoWar Mar 02 '16 at 19:44
3

I am a little late to this. However, if you are using Owin to self host and need windows auth. In your startup class you can add the following.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"];
        listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
    }
}
IdahoSixString
  • 643
  • 10
  • 18
  • I wanted to put together a simple self hosted web api as a windows service. when using Owin , accessing the webapi via browser always promoted for login/password (entering the username password does seem to work as expected and the user is authenticated) but is there a way to void the login prompt ? – dotnetcoder Feb 13 '20 at 19:15
2

Similar to tpeczek's answer but updated to reflect HTTPS usage. tpeczek's answer doesn't work for HTTPS because the call to base.OnConfigureBinding(httpBinding); with HTTPS overwrites the changes. Additionally, you cannot use httpBinding.Security.Mode = HttpBindingSecurityMode.TransportCredentialOnly; with HTTPS.

Use a custom HttpSelfHostConfiguration:

public class NtlmSelfHostConfiguration : HttpSelfHostConfiguration
{
    public NtlmSelfHostConfiguration(string baseAddress)
        : base(baseAddress)
    { }

    public NtlmSelfHostConfiguration(Uri baseAddress)
        : base(baseAddress)
    { }

    protected override BindingParameterCollection OnConfigureBinding(
        HttpBinding httpBinding)
    {
        if (this.BaseAddress.Scheme == Uri.UriSchemeHttps)
        {
            var ret = base.OnConfigureBinding(httpBinding);
            httpBinding.Security.Transport.ClientCredentialType =
                HttpClientCredentialType.Ntlm;
            return ret;
        }

        httpBinding.Security.Mode = HttpBindingSecurityMode.TransportCredentialOnly;
        httpBinding.Security.Transport.ClientCredentialType = 
            HttpClientCredentialType.Ntlm;
        return base.OnConfigureBinding(httpBinding);
    }
}

Then, you can do

var config = new NtlmSelfHostConfiguration("http://myComputerName:8080");

or

var config = new NtlmSelfHostConfiguration("https://myComputerName:8443");

to get a configuration to pass into new HttpSelfHostServer(config)

Community
  • 1
  • 1
mheyman
  • 4,211
  • 37
  • 34
  • Hi please take a look at my question here http://stackoverflow.com/questions/35751411/authentication-in-self-host-web-api – NoWar Mar 02 '16 at 19:43
2

Are you sure you're getting through the authentication part? You could use fiddler to check whether the requests are actually going through or whether the server always responds with 401 Unauthorized (since you're using authentication).

You could also try to implement your own custom AuthorizeAttribute and put breakpoints in it to make sure it gets hit (you'll want to override the OnAuthorization method and see if that gets hit).

using System.Web.Http;
public class MyAuth : AuthorizeAttribute
{
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        base.OnAuthorization(actionContext); //put breakpoint here
    }
}

Also, make sure you're using the Authorize attribute from System.Web.Http, and not from System.Web.Mvc. See here why.

Community
  • 1
  • 1
Szilard Muzsi
  • 1,881
  • 2
  • 16
  • 20
  • Everything works when deployed to IIS (or hosted through visual studio), it's just the self host stuff that's not working. What I've done for now is make the self-host stuff use basic authentication with a delegating handler, I only need it for integration tests so that code won't be going into production. Assuming that all works it seems that the only part that isn't working in the windows authentication... I tried what you suggested, the breakpoint never gets hit. – jonnii Mar 23 '12 at 15:15
  • Well that's fishy. I've created a simple project to reproduce your issue, and the Windows authentication doesn't work for me either in a self-hosted version. As I checked in fiddler though, that was because it never returned a challenge-message to the client, it just simply returned unauthorized(since no authentication token is sent until a challenge reply is received). It did, however, hit my breakpoint in my custom `OnAuthorization` method. You used the `[MyAuth]` tag instead of `[Authorize]`, I assume. – Szilard Muzsi Mar 25 '12 at 00:48
  • This is exactly what I was seeing. Without the challenge you're never going to be authenticated. I'm pretty sure this is a bug in self-host... – jonnii Mar 26 '12 at 13:40
  • It seems like a bug to me as well. You could try to implement your own custom `MessageHandler` to add the correct challenge headers to the response... but I'm not sure that would be enough (the authentication mechanisms involves several steps, and you shouldn't be reinventing the wheel here). – Szilard Muzsi Mar 26 '12 at 14:40
1

Just to add, if you're using tpeczek's solution and also using HttpClient, you might need to do this:

        var handler = new HttpClientHandler();
        handler.UseDefaultCredentials = true;
        _httpClient = new HttpClient(handler);
Allan Elder
  • 4,052
  • 17
  • 19
1

Have you tried putting the [Authorize] attribute on your controller?

[Authorize]
public class HelloController : ApiController
Eric King
  • 11,594
  • 5
  • 43
  • 53
  • I added the [Authorize] attribute to my controller, but now I just receive a bare bones HTML page with no content between the body tags. I'm hitting the page with IE. – Dave Johnson Mar 05 '12 at 21:27
1

Here is a link to a short video explaining how to use authorization.

http://www.asp.net/web-api/videos/getting-started/authorization

In essence use the [Authorize] attribute on the class, catch the error and return an HTTP 401 response and then have the client detect it and go to the login page

PTRMark
  • 27
  • 2
0

Related answer for whom need it, about basic auth with token

Merging some help, info, answers and a self auth system that I made for a real Web API I could finally use roles and attributes tags for this. Is made for Authorization tag in the header.

Server invocation:

 var config = new HttpSelfHostConfiguration("http://localhost:8080");
            config.UserNamePasswordValidator = new PHVValidator();
            config.Routes.MapHttpRoute(
                "API Default", "{controller}/{id}",
                new { id = RouteParameter.Optional });

            using (HttpSelfHostServer server = new HttpSelfHostServer(config))
            {
                server.OpenAsync().Wait();
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new DominusForm());
            }

Auth Method: (hardcoded for ex. only, choose user, pass and roles from anywhere)

    public class PHVValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (userName == "admin" && password == "123")
            {
                string[] rolarray = new string[] { "admin" };
               IPrincipal principal = new GenericPrincipal(new GenericIdentity(userName), rolarray);
                Thread.CurrentPrincipal = principal;
            }
        }
    }

Method:

[Authorize(Roles = "admin")]
public HttpResponseMessage Get()
{
     do things
}
Leandro Bardelli
  • 10,561
  • 15
  • 79
  • 116