3

I have a self hosted REST WCF Windows Service. I've got Basic Authentication working for the service, but I would like to also support Windows Authentication for clients which support it. Would I have to have a separate endpoint on a different port?

UPDATE: I've gotten something close to working in WCF 4.0. Here is the code, the issue I'm having now is that I can only seem to get NTLM working, which requires the user to type in their credentials, which nullifies any benefits to using Windows Auth.

I'm still not sure how to get Windows Authentication working, without requiring the user to enter their password in a second time.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.IdentityModel.Selectors;

namespace BasicAndNegotiateAuth
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri newUri = new Uri(new Uri("http://localhost/"), "/");
            WebServiceHost webHost = new WebServiceHost(typeof(HelloWorldService), newUri);

            // TransportCredentialOnly means we can use http
            WebHttpBinding binding = new WebHttpBinding(WebHttpSecurityMode.Transport);
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic | HttpClientCredentialType.Ntlm;

            ServiceEndpoint ep = webHost.AddServiceEndpoint(typeof(IHelloWorld), binding, newUri);

            WebHttpBehavior wb = new WebHttpBehavior();                
            ep.EndpointBehaviors.Add(wb);
            ep.Behaviors.Add(new WebHttpCors.CorsSupportBehavior());

            //ServiceAuthenticationBehavior sab = null;
            //sab = webHost.Description.Behaviors.Find<ServiceAuthenticationBehavior>();
            //if (sab == null)
            //{
            //    sab = new ServiceAuthenticationBehavior();
            //    sab.AuthenticationSchemes = AuthenticationSchemes.Basic | AuthenticationSchemes.IntegratedWindowsAuthentication;
            //    host.Description.Behaviors.Add(sab);
            //}
            webHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
            webHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();

            webHost.Open();
            Console.ReadLine();
        }
    }

    public class CustomUserNameValidator: UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            int i = 1;
        }
    }

    [ServiceContract]
    public interface IHelloWorld
    {
        [System.ServiceModel.OperationContract]
        [System.ServiceModel.Web.WebGet(
            UriTemplate = "/",
            ResponseFormat = WebMessageFormat.Json)]
        string GetHello();
    }

    public class HelloWorldService : IHelloWorld
    {
        public string GetHello()
        {
            ServiceSecurityContext ssc = ServiceSecurityContext.Current;
            return "Hello World";
        }
    }
}
bpeikes
  • 3,495
  • 9
  • 42
  • 80

1 Answers1

2

In .NET 4.5, you can support multiple authentication schemes on a single endpoint in WCF.

Here is an example of how you would do it in code for a Self-Hosted Service:

ServiceAuthenticationBehavior sab = null;
sab = serviceHost.Description.Behaviors.Find<ServiceAuthenticationBehavior>();
if (sab == null)
{
    sab = new ServiceAuthenticationBehavior();
    sab.AuthenticationSchemes = AuthenticationSchemes.Basic | 
           AuthenticationSchemes.Negotiate | AuthenticationSchemes.Digest;
    serviceHost.Description.Behaviors.Add(sab);
}
else
{
     sab.AuthenticationSchemes = AuthenticationSchemes.Basic | 
           AuthenticationSchemes.Negotiate | AuthenticationSchemes.Digest;
}

Alternatively, you can set it up in your config file like this:

<behaviors>
    <serviceBehaviors>
      <behavior name="limitedAuthBehavior">
        <serviceAuthenticationManager authenticationSchemes=
                                             "Negotiate, Digest, Basic"/>
        <!-- ... -->
      </behavior>
   </serviceBehaviors>
</behaviors>

Then specify InheritedFromHost in your binding settings like this:

<bindings>
   <basicHttpBinding>
      <binding name="secureBinding">
        <security mode="Transport">
          <transport clientCredentialType="InheritedFromHost" />
        </security>
      </binding>
   </basicHttpBinding>
</bindings>

See this article on MSDN: Using Multiple Authentication Schemes with WCF.

Derek W
  • 9,708
  • 5
  • 58
  • 67
  • Does this only work in 4.5? Also, it's not clear, which authentication scheme is Windows Auth in the example you provided? – bpeikes May 15 '14 at 14:10
  • `AuthenticationSchemes.IntegratedWindowsAuthentication` is what you are looking for to set up **Windows authentication**. Yes, this is only in .NET 4.5 and later. – Derek W May 15 '14 at 14:30
  • Couple of questions, if we put AuthenticationSchemes.Basic and AuthenticationSchemes.IntegratedWindowsAuthentication which one gets tried first? – bpeikes May 15 '14 at 15:39
  • Also, if I specify AuthenticationSchemes.Basic, how to we handle the Authentication ourselves? i.e. we want to authenticate login and password against a database? – bpeikes May 15 '14 at 15:39
  • Finally, once authenticated, how do we access the authenticated user from our code? Once authenticated, we need to see if the user should have access to specific records in the database. – bpeikes May 15 '14 at 15:40
  • (1) I'm unsure of the order of precedence in which authentication scheme gets tried first. You could do some trials to determine this - That's a really good question. – Derek W May 16 '14 at 15:58
  • (2) For a Self-Hosted Service when using Basic Authentication - it is handled by the `UserNamePasswordValidator`. Similar to when `UserName` is specified for `ClientCredentialType` for other bindings. – Derek W May 16 '14 at 16:03
  • 1
    (3) That's kind of out of the scope of this question. But there are a few questions existing on SO that address this: http://stackoverflow.com/questions/11566182/how-do-i-restrict-access-to-some-methods-in-wcf and http://stackoverflow.com/questions/1343060/restrict-wcf-web-service-functionality-based-on-user-group – Derek W May 16 '14 at 16:07
  • (1b) I was actually thinking about it some more and when you make a call to the REST service your HTTP request header will specify the credential type (Basic, etc). So WCF should select the authentication scheme based on that information. – Derek W May 17 '14 at 18:45
  • In your sample above, how do you setup a custom UserNamePasswordValidator for just the Basic authentiction, but let the ServiceAuthenticationBehavior deal with WindowsAuthentication? Specifically if we were self hosting and configuring in code? – bpeikes May 27 '14 at 18:25
  • The code you provided does not work. I've come closer with code, which I've posted above, but I can only seem to get NTLM working. – bpeikes May 30 '14 at 13:59