0

I want to create a WCF console self hosted - server side authentication with Certificate - rest service.

I encountered a problem with actually invoking the Service, since i always get in the response 401 Unauthorized.

Since this is a "One-way" authentication, where the service is identifying itself to the client, why would i always get a 401 unautorized response as a client application (as if the client needs to identify itself to the service to access its resources?)

Could anyone help me with pinpointig where i am going wrong, and how to get my client-service communication working finally?

The Simple Service contract:

[ServiceContract]
public interface IService1
{

    [OperationContract]
    [WebGet(UriTemplate = "Students", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    List<Student> GetStudentDetails();

    // TODO: Add GetMethod with parameter 
    [OperationContract]
    [WebGet(UriTemplate = "Student/{id}", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    Student GetStudentWithId(string id);

    //TODO: add one post method here
    [OperationContract]
    [WebInvoke(Method="POST", UriTemplate = "Student/New", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    void NewStudent(Stream stream);

    //TODO: add one post method here
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "Student/NewS", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    void NewStudentS(Student stream);
}

// Use a data contract as illustrated in the sample below to add composite types to service operations.
[DataContract]
public class Student
{
    [DataMember]
    public int ID
    {
        get;
        set;
    }

    [DataMember]
    public string Name
    {
        get;
        set;
    }
}

Service Implementation:

public class Service1 : IService1
{
    public List<Student> GetStudentDetails()
    {
        return new List<Student>() { new Student() { ID = 1, Name = "Goran" } };
    }

    public Student GetStudentWithId(string id)
    {
        return new Student() { ID = Int32.Parse(id), Name = "Ticbra RanGo" };
    }

    public void NewStudent(Stream stream)
    {
        using(stream)
        {
            // convert Stream Data to StreamReader
            StreamReader reader = new StreamReader(stream);
            var dataString = reader.ReadToEnd();

            Console.WriteLine(dataString);
        }
    }

    public void NewStudentS(Student student)
    {
        Console.WriteLine(student.Name);
    }
}

My console application running the service:

static void Main(string[] args)
    {
        Uri httpUrl = new Uri("https://localhost:8080/TestService");
        using (WebServiceHost host = new WebServiceHost(typeof(Service1)))
        {
             // Create the binding.  
            WSHttpBinding binding = new WSHttpBinding();
            binding.Name = "binding1";
            binding.Security.Mode = SecurityMode.Transport;


            host.AddServiceEndpoint(typeof(IService1), binding, httpUrl/*"rest"*/);
            // Enable metadata publishing.
            ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            host.Description.Behaviors.Add(smb);              

            //Add host certificate to service wcf for identification
            host.Credentials.ServiceCertificate.SetCertificate(
                StoreLocation.LocalMachine,
                StoreName.My,
                X509FindType.FindBySubjectName,
                "localhost");

            host.Open();

            foreach (ServiceEndpoint se in host.Description.Endpoints)
                Console.WriteLine("Service is host with endpoint " + se.Address);

            Console.WriteLine("Host is running... Press < Enter > key to stop");
            Console.ReadLine();
            host.Close();
        }

        //Console.WriteLine("ASP.Net : " + ServiceHostingEnvironment.AspNetCompatibilityEnabled);
        Console.WriteLine("Host is running... Press < Enter > key to stop");
        Console.ReadLine();
    }

Note that i create the certificate root and children through KeyStore Explorer, and placed them approriately in personal and trusted root certificates on windows. Certificates

I mapped the server certificate to the port 8080 though CMD.

The clients i was using are SOAPUI, and my manual coded client. Client Code:

        WebRequest request = HttpWebRequest.Create(urlTextBox.Text);

        var webResponse = request.GetResponse();

        using (Stream dataStream = webResponse.GetResponseStream())
        {
            // Open the stream using a StreamReader for easy access.  
            StreamReader reader = new StreamReader(dataStream);
            // Read the content.  
            string responseFromServer = reader.ReadToEnd();
            // Display the content.  
            Console.WriteLine(responseFromServer);
            HttpResonseTextBox.Text = responseFromServer;
        }

Best regards and thank you in advance so much

N. berouain
  • 1,181
  • 13
  • 17
Goran
  • 3
  • 2

2 Answers2

0

You are mapping the certificate to the port 8080 to get https protocol working. It is fine until this moment.

However 401 error means that service requires some credentials from the client (and raises 401 error if any)

Please try to remove (or comment) SetCertificate method call like below

           //Add host certificate to service wcf for identification
            //host.Credentials.ServiceCertificate.SetCertificate(
            //    StoreLocation.LocalMachine,
            //    StoreName.My,
            //    X509FindType.FindBySubjectName,
            //    "localhost");

and try will it work or not. Just to check

Wcf transport security with certificate requires client to specify certificate too like it is documented.

I'm not sure that you can consume wcf service with certificate authentication using the soap protocol with HttpWebRequest. It requires wcf client with SetCertificate method to be used:

// The client must specify a certificate trusted by the server.  
cc.ClientCredentials.ClientCertificate.SetCertificate(  
    StoreLocation.CurrentUser,  
    StoreName.My,  
    X509FindType.FindBySubjectName,  
    "contoso.com");  

(this is sample from the documentation)

oleksa
  • 3,688
  • 1
  • 29
  • 54
0

WSHttpBinding binding = new WSHttpBinding();
binding.Name = "binding1";
binding.Security.Mode = SecurityMode.Transport;

The above code takes the windows authentication as the way to authenticate the client.

        //this is the default value unless we specify it manually.
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;

Therefore, we should call it on the client-side by providing a windows credential. Besides, this kind of WCF service is not called Rest API, it is called SOAP web service. We usually invoke it by using a client proxy.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/accessing-services-using-a-wcf-client
Then setting up windows credential and call the method.

            ServiceReference1.ServiceClient client = new ServiceReference1.ServiceClient();
            //these are windows accounts on the server-side.
            client.ClientCredentials.Windows.ClientCredential.UserName = "administrator";
            client.ClientCredentials.Windows.ClientCredential.Password = "123456";
            var result = client.Test();
            Console.WriteLine(result);

If we want to create a rest service, please take the Webhttpbinding to create the service.

            WebHttpBinding binding = new WebHttpBinding();
            binding.Security.Mode = WebHttpSecurityMode.Transport;
            //this is default value. 
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

Likewise, we need to bind a certificate to the particular port in order to make the transport layer security available.

netsh http add sslcert ipport=0.0.0.0:8000 certhash=0000000000003ed9cd0c315bbb6dc1c08da5e6 appid={00112233-4455-6677-8899-AABBCCDDEEFF}

Netsh Http command.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate
Due to that, the security mode of authenticating the client is HttpClientCredentialType.None. We needn’t provide windows credentials on the client-side.
Feel free to let me know if there is anything I can help with.

Abraham Qian
  • 7,117
  • 1
  • 8
  • 22
  • Hi Abraham. Thank you for your detailed reply. It seems that the problem was in the wrong binding type i used and in not setting the ClientCredentialType To "none". Your help is greatly appreciated! – Goran Jan 09 '20 at 18:24