none
Good clear simple example of authenticating users calling a WCF service using SSL please RRS feed

  • Question

  • I have searched books and the web for this but so far have not managed to find a good working example/sample project for the following (I hope very standard!) situation:

    For an ASP.NET MVC5 web app, hosted in Azure (and already using SSL with a digital certificate), with users authenticated via username and password in the usual (DefaultConnection type) database, we also want users to be able to call some WCF services from a WPF desktop app, and we need to authenticate and authorise calls from those users (using their usernames and passwords).  From the documentation, I think that I need to do is use Transport security with clientCredentialType="Basic", but haven't managed to get this to work in my experiments.

    Apologies if this has already been answered, but despite spending a lot of time searching (and reading books), so far I have only found either very complicated examples (eg using a mixture of transport and message security, which seems over the top), or ones which are out of date, or which don't work when I try them out (because they are missing some crucial bit of information).

    Thanks

    Monday, November 2, 2015 11:52 AM

Answers

  • Hi PatrickL,

    I create a project to reproduce your issue. I got the same error. Finally, it works.

    Here is my web config like below:

    <system.serviceModel>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true"></serviceHostingEnvironment>
        <behaviors>
          <serviceBehaviors>
            <behavior name="userNameBehavior">
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
              <serviceDebug  includeExceptionDetailInFaults="true"/>
              <serviceCredentials>
                <issuedTokenAuthentication allowUntrustedRsaIssuers="true"></issuedTokenAuthentication>
                <clientCertificate>
                  <authentication certificateValidationMode="None"/>
                </clientCertificate>
                <serviceCertificate findValue="sven.com" storeName="Root" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/>
                <!--custom validation-->
                <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfService1.MyValidator,WcfService1"/>
              </serviceCredentials>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <basicHttpBinding>
            <binding name="basicbinding">
                  <security mode="TransportWithMessageCredential">
                        <transport clientCredentialType="None" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                  </security>
                 </binding>
             </basicHttpBinding>
    
         
        </bindings>
        <services>
          <service behaviorConfiguration="userNameBehavior" name="WcfService1.Service1">
            <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basicbinding"
              name="username" contract="WcfService1.IService1" />
          </service>
        </services>
    
      </system.serviceModel>

    I used basicbinding, then I published it to IIS with SSL. Last I create a client to test it.

    Host it on IIS, and run it:

    My client result:

    Client code:

    static void Main(string[] args)
            {
                Util.SetCertificatePolicy();
    
                ChannelFactory<IService1> cf = new ChannelFactory<IService1>("username");
                cf.Credentials.UserName.UserName = "user";
                cf.Credentials.UserName.Password = "pwd";
    
                IService1 proxy = cf.CreateChannel();
                Console.WriteLine(proxy.GetData(2));
    
                ((IClientChannel)proxy).Close();
                Console.ReadKey();
    }

    My custom validator code:

    namespace WcfService1
    {
        class MyValidator:UserNamePasswordValidator
        {
           
            public override void Validate(string userName, string password)
            {
                
                if (userName != "user" || password != "pwd" )
                {
                    throw new Exception("Unknown Username or Password");
                }
            }
        }
    }

    For how to host WCF Service you can refer to the following articles:

    1.Creating and deploying a WCF service on Windows Azure and consuming it in Windows 8 Store App

    I hope that can be helpful to you.

    Best Regards,

    Grady


    Thursday, November 5, 2015 9:34 AM
    Moderator

All replies

  • Hi PatrickL,

    According to your description, as far as I know, when we use the transport security with

    clientCerdentialType="Basic", we can use the username and password to authenticate.

    And, we need to make sure  the Web service already has an SSL implementation that can

    be used.

    Code shown as below:

    WSHttpBinding myBinding = new WSHttpBinding();
    myBinding.Security.Mode = SecurityMode.Transport;
    myBinding.Security.Transport.ClientCredentialType =
        HttpClientCredentialType.Basic;
    
    // Create the endpoint address. Note that the machine name 
    // must match the subject or DNS field of the X.509 certificate
    // used to authenticate the service. 
    EndpointAddress ea = new
        EndpointAddress("https://machineName/Calculator");
    
    // Create the client. The code for the calculator 
    // client is not shown here. See the sample applications
    // for examples of the calculator code.
    CalculatorClient cc =
        new CalculatorClient(myBinding, ea);
    // The client must provide a user name and password. The code
    // to return the user name and password is not shown here. Use
    // a database to store the user name and passwords, or use the 
    // ASP.NET Membership provider database.
    cc.ClientCredentials.UserName.UserName = ReturnUsername();
    cc.ClientCredentials.UserName.Password = ReturnPassword();
    try
    {
        // Begin using the client.
        cc.Open();
        Console.WriteLine(cc.Add(100, 11));
        Console.ReadLine();
    
        // Close the client.
        cc.Close();
    }
    

    Also, we can use the custom username and password validator in IIS.

    For more information, please refer to the following articles:

    1.WCF Service over HTTPS with custom username and password validator in IIS

    Best Regards,

    Grady

    Tuesday, November 3, 2015 1:56 AM
    Moderator
  • Thanks, but that article is from 2010 (and hence quite possibly out of date - at least that's what I found with many similar web articles), and doesn't refer to Azure (where my WCF service is hosted), and says that

    "We defined WsHttpBinding with security mode TransportWithMessageCredentials. You might ask, why not Transport. The answer is, if we want to use a simple custom username validator under IIS, we must useTransportWithMessageCredentials, even it has a large overhead. Under IIS, a custom validator will not work with Transport security. At least, I couldn't get it to work, and it is also mentioned here. "

    I think this illustrates a major problem (certainly I find it very frustrating) with WCF: its lack of simplicity, and lack of documentation, even for what surely are fairly common/standard situations:

    Why on earth should TransportWithMessageCredentials, with "a large overhead" be needed in a common situation (ie allowing secure calls to WCF)?

    Why is a custom validator needed? (When a membership database already exists for the website).

    Why is a X509Certificate needed? Presumably that is in addition to the existing SSL certificate for the website mydomain.com (if the service is at https://mydomain.com/Service1.svc)?

    The article also talks about configuring IIS, but as mentioned above, our website and service are in Azure. (I know how to configure Azure for SSL for the website, so maybe no configuration is needed, but, as I said above, the article you linked to is 5 years old, so gives no guidance for Azure).


    Tuesday, November 3, 2015 11:26 PM
  • Hi PatrickL,

    I create a project to reproduce your issue. I got the same error. Finally, it works.

    Here is my web config like below:

    <system.serviceModel>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true"></serviceHostingEnvironment>
        <behaviors>
          <serviceBehaviors>
            <behavior name="userNameBehavior">
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
              <serviceDebug  includeExceptionDetailInFaults="true"/>
              <serviceCredentials>
                <issuedTokenAuthentication allowUntrustedRsaIssuers="true"></issuedTokenAuthentication>
                <clientCertificate>
                  <authentication certificateValidationMode="None"/>
                </clientCertificate>
                <serviceCertificate findValue="sven.com" storeName="Root" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/>
                <!--custom validation-->
                <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfService1.MyValidator,WcfService1"/>
              </serviceCredentials>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <basicHttpBinding>
            <binding name="basicbinding">
                  <security mode="TransportWithMessageCredential">
                        <transport clientCredentialType="None" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                  </security>
                 </binding>
             </basicHttpBinding>
    
         
        </bindings>
        <services>
          <service behaviorConfiguration="userNameBehavior" name="WcfService1.Service1">
            <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basicbinding"
              name="username" contract="WcfService1.IService1" />
          </service>
        </services>
    
      </system.serviceModel>

    I used basicbinding, then I published it to IIS with SSL. Last I create a client to test it.

    Host it on IIS, and run it:

    My client result:

    Client code:

    static void Main(string[] args)
            {
                Util.SetCertificatePolicy();
    
                ChannelFactory<IService1> cf = new ChannelFactory<IService1>("username");
                cf.Credentials.UserName.UserName = "user";
                cf.Credentials.UserName.Password = "pwd";
    
                IService1 proxy = cf.CreateChannel();
                Console.WriteLine(proxy.GetData(2));
    
                ((IClientChannel)proxy).Close();
                Console.ReadKey();
    }

    My custom validator code:

    namespace WcfService1
    {
        class MyValidator:UserNamePasswordValidator
        {
           
            public override void Validate(string userName, string password)
            {
                
                if (userName != "user" || password != "pwd" )
                {
                    throw new Exception("Unknown Username or Password");
                }
            }
        }
    }

    For how to host WCF Service you can refer to the following articles:

    1.Creating and deploying a WCF service on Windows Azure and consuming it in Windows 8 Store App

    I hope that can be helpful to you.

    Best Regards,

    Grady


    Thursday, November 5, 2015 9:34 AM
    Moderator