2

UPDATE (8/7/2014) - The solution to this problem was that I needed to add a class that derived from "UserNamePasswordValidator" and register it in Web.Config.

I have created a simple test WCF service and test console client application (see below for code). I am using .NET 4.5.1. I have already searched for duplicates on StackOverflow (found similar posts here and here) - however I feel that the referenced posts are potentially outdated, and also feel that my post is more limited in scope.

OK now for the example:

The solution currently uses sessions (in ITestService.cs):

[ServiceContract(SessionMode = SessionMode.Required)]

... and uses wsHttpBinding (see below app.config and web.config).

When I deploy this to a server, I am successfully able to access it via a web browser using HTTPS like this: https://myserver.com/test/testservice.svc

However, when I change the endpoint in the client app.config from:

http://localhost:20616/TestService.svc/TestService.svc

to:

https://myserver.com/test/testservice.svc

and run the console application again, I receive the error: "The provided URI scheme 'https' is invalid; expected 'http'. Parameter name: via"

My question is, what is the minimum changes I need to make for this to work, without changing SessionMode.Required?

Here is the client console application code. Please be sure to change the App.Config value for "mycomputer\Matt" to the correct value for your machine.

Program.cs

using System;

namespace TestClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Clear();
            Console.WriteLine("Attempting to log in...");
            try
            {
                TestServiceReference.TestServiceClient client = new TestServiceReference.TestServiceClient();

                bool loginSuccess = client.LogIn("admin", "password");
                if (loginSuccess)
                {
                    Console.WriteLine("Successfully logged in.");
                    string secretMessage = client.GetSecretData();
                    Console.WriteLine("Retrieved secret message: " + secretMessage);
                }
                else
                {
                    Console.WriteLine("Log in failed!");
                }
            }
            catch (Exception exc)
            {
                Console.WriteLine("Exception occurred: " + exc.Message);
            }
            Console.WriteLine("Press ENTER to quit.");
            Console.ReadLine();
        }
    }
}

App.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/>
    </startup>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_ITestService"/>
            </wsHttpBinding>
        </bindings>
        <client>
          <endpoint address="https://myserver.com/test/testservice.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ITestService" contract="TestServiceReference.ITestService" name="WSHttpBinding_ITestService">
            <identity>
              <userPrincipalName value="mycomputer\Matt"/>
            </identity>
          </endpoint>
            <!--<endpoint address="http://localhost:20616/TestService.svc/TestService.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ITestService" contract="TestServiceReference.ITestService" name="WSHttpBinding_ITestService">
                <identity>
                    <userPrincipalName value="mycomputer\Matt"/>
                </identity>
            </endpoint>-->
        </client>
    </system.serviceModel>
</configuration>

WCF Service code.
ITestService.cs:

using System.ServiceModel;

namespace WcfSessionsOverHttpsTest
{
    [ServiceContract(SessionMode = SessionMode.Required)]
    public interface ITestService
    {

        [OperationContract(IsInitiating = true)]
        bool LogIn(string username, string password);

        [OperationContract(IsInitiating = false, IsTerminating = true)]
        bool LogOut();

        [OperationContract(IsInitiating = false)]
        string GetSecretData();
    }
}

TestService.svc:

namespace WcfSessionsOverHttpsTest
{    
    public class TestService : ITestService
    {
        public bool IsAuthenticated { get; set; }
        bool ITestService.LogIn(string username, string password)
        {
            if (username == "admin" && password == "password")
            {
                IsAuthenticated = true;
                return true;
            }
            else
            {
                IsAuthenticated = false;
                return false;
            }
        }

        bool ITestService.LogOut()
        {
            IsAuthenticated = false;
            return true;
        }

        string ITestService.GetSecretData()
        {
            if (!IsAuthenticated)
            {
                throw new System.Security.Authentication.AuthenticationException("User has not logged in.");
            }
            else
            {
                string secretMessage = "The Red Sox are going to win the World Series in 2016";
                return secretMessage;
            }
        }
    }
}

Web.config:

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5.1"/>
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="wsHttpEndpointBinding" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
        </binding>
      </wsHttpBinding>
    </bindings>
    <services>
      <service name="WcfSessionsOverHttpsTest.TestService">
        <endpoint address="/TestService.svc" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" contract="WcfSessionsOverHttpsTest.ITestService"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <add binding="wsHttpBinding" scheme="http"/>
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

Thanks in advance for any help!

Matt

Community
  • 1
  • 1
BlueSky
  • 1,449
  • 1
  • 16
  • 22
  • 1
    As a minimum to enable HTTPS/SSL you need `` in your binding. – 500 - Internal Server Error Jul 31 '14 at 17:18
  • I added to both the client and service bindings. Now I get the following exception message on the client: "Contract requires Session, but Binding 'WSHttpBinding' doesn't support it or isn't configured properly to support it." – BlueSky Jul 31 '14 at 17:27
  • Adding: to both the client and service bindings results in There was no endpoint listening at https://myserver.com/test/testservice.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. Inner Exception message: The remote server returned an error: (404) Not Found. – BlueSky Jul 31 '14 at 17:41
  • "The remote server returned an error: (404) Not Found." is obviously a misleading error. https://myserver.com/test/testservice.svc still works in a browser (note: the domain is obviously not myserver.com, but I've used that to hide my actual domain). Thank you. Any ideas anyone? – BlueSky Jul 31 '14 at 18:16
  • I don't have specific experience with your exact (desired) configuration. My suggestion would be to enable WCF tracing and to take a look at the resulting logs. They often have more verbose diagnostics than just the exception message. – 500 - Internal Server Error Jul 31 '14 at 18:18
  • Basically at a dead-end it seems. SessionMode.Required doesn't seem to be well documented or supported by Microsoft. WCF tracing and resulting logs yielded no additional hints. – BlueSky Jul 31 '14 at 20:45

1 Answers1

1

The solution to this problem was that I needed to add a class that derived from "UserNamePasswordValidator" and register it in Web.Config.

public class CustomUserNameValidator : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        return;
    }
}

Web.config:

<behaviors>
  <serviceBehaviors>
    <behavior>
      <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
      <serviceMetadata httpsGetEnabled="true" />
      <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
      <serviceDebug includeExceptionDetailInFaults="true" />
      <serviceCredentials>
        <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyProgram.CustomUserNameValidator,MyProgram" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>
BlueSky
  • 1,449
  • 1
  • 16
  • 22