3

I have a self-hosted WCF duplex service that is using the NetHttpsBinding. It is set to use custom username/password authentication via a UserNamePasswordValidator for client authentication and a certificate for service authorization. Connection works fine, but the UserNamePasswordValidator is never called, so I can connect with whatever username and password combination. So my question is why is it ignoring my UserNameValidator?

Here is the full config file of the service:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
  </configSections>
  <system.serviceModel>
    <services>
      <service name="SchoolTestMaker.Service.SchoolTestMakerService">
        <endpoint address="" binding="netHttpsBinding" bindingConfiguration="netHttpsEndpointBinding" contract="SchoolTestMaker.Service.ISchoolTestMakerService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
          <serviceDebug includeExceptionDetailInFaults="True" />
          <!--<serviceAuthorization principalPermissionMode="Custom" />-->
          <serviceCredentials>
            <serviceCertificate findValue="**classified(FBI)**"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <netHttpsBinding>
        <binding name="netHttpsEndpointBinding">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </netHttpsBinding>
    </bindings>
  </system.serviceModel>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
</configuration>

The UserNamePasswordValidator:

public class SchoolTestMakerServiceUserNamePasswordValidator:UserNamePasswordValidator
    {
        IUnitOfWork unitOfWork;
        IHashGenerator hashGenerator;
        public SchoolTestMakerServiceUserNamePasswordValidator(IUnitOfWork unitOfWork,IHashGenerator hashGenerator)
        {
            this.unitOfWork = unitOfWork;
            this.hashGenerator = hashGenerator;
        }
        public override void Validate(string userName, string password)
        {
            throw new Exception();
            /*if(userName==null||password==null)
            {
                throw new ArgumentNullException();
            }
            string passwordHash = hashGenerator.GenerateHash(password);
            UserAccount userAccount = unitOfWork.Repository<UserAccount>().Get(x => x.UserName == userName && x.PasswordHash == passwordHash);
            if(userAccount==null)
            {
                throw new SecurityTokenException("Unknown Username or Incorrect Password");
            }*/
        }
    }

StartService method:

public void StartService()
        {
            serviceHost = new UnityServiceHost(container,
                typeof(SchoolTestMakerService), new Uri(endpointAddress));

            //Console.WriteLine(((WSDualHttpBinding)serviceHost.Description.Endpoints[0].Binding).Security.Message.ClientCredentialType);

            serviceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
            serviceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = userNamePasswordValidator;

            /*var externalAuthorizationPolicies = new ReadOnlyCollection<IAuthorizationPolicy>(new IAuthorizationPolicy[] { authorizationPolicy });
            ServiceAuthorizationBehavior authorizationBehavior=serviceHost.Description.Behaviors.Find<ServiceAuthorizationBehavior>();
            authorizationBehavior.ExternalAuthorizationPolicies = externalAuthorizationPolicies;*/

            serviceHost.Open();
            ServiceRunning = true;
        }
Phoenix
  • 913
  • 1
  • 8
  • 18
  • Is your endpoint https? If not.. well: http://stackoverflow.com/questions/12828295/wcf-custom-username-authentication-using-http – TGlatzer Dec 19 '14 at 12:54
  • Well, no. WSDualHttpBinding doesn't support transport security which means no https. – Phoenix Dec 19 '14 at 13:06
  • If the linked SO is true, you will not be able to use CustomUsername Auth then.. – TGlatzer Dec 19 '14 at 13:10
  • Is there anything I can do about it? Because I surely want to avoid passing clear credentials. Any binding that supports both duplex and ssl? Custom binding maybe? – Phoenix Dec 19 '14 at 13:12
  • I think the netTCPbindings will support duplex and transport security: http://msdn.microsoft.com/en-us/library/ms731092(v=vs.110).aspx – TGlatzer Dec 20 '14 at 12:14
  • The problem with netTCPBinding is that you need to open ports on the client side. Yesterday I found about netHTTPBinding from one of the links you sent. It supports Duplex, transport and message security and also uses WebSockets for communication which means I don't need to open ports on the client side since it uses port 80/443 for communication in both directions. I am probably gonna be using that but I have some problems with Visual Studio not letting me add a Service Reference when I switch to netHTTP from wsHTTP. – Phoenix Dec 20 '14 at 12:56
  • Perhaps you add it from another binding and change the app.config? – TGlatzer Dec 20 '14 at 13:00
  • Yes, I thought about that, but it seems kinda sketchy don't you think? Most likely gonna use that though sketchy or not if nothing else works. – Phoenix Dec 20 '14 at 13:03
  • I would not call it sketchy - add service reference just calls svcutil to create classes.. you can do that all alone if you want to. Perhaps you can call the svcutil manually?! – TGlatzer Dec 20 '14 at 13:14
  • I was just able to connect using netHttpsBinding, but my UserNamePasswordValidator was once again ignored(poor fella). The problem with visual studio not wanting to add a service reference was that I hadn't registered the certificate with the port I was using for the service(since I was trying to use https). – Phoenix Dec 20 '14 at 13:59
  • What exactly is your question? Sum it up in one sentence. – RubberDuck Dec 20 '14 at 14:58

1 Answers1

0

If you want to use custom UserNamePassword Validation, you have to tell WCF which class will handle the Validation:

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <serviceCredentials>
          <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="CustomUsernamePasswordAuth.Service.UserNamePassValidator, CustomUsernamePasswordAuth.Service" />
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>
TGlatzer
  • 5,815
  • 2
  • 25
  • 46