tags:

views:

497

answers:

2

Having an issue while making POX REST call using WCF with WebHttpBinding set to Basic Authentication (HttpClientCredentialType.Basic)

Instead of one call from the client with “Authorization: Basic” specified in HTTP Header, two calls are made. First call without authentication at all to which service responses with 401 Unauthorized error, second call with proper authentication info.

This seems to be handled by WCF service with no hiccup at all. Calling third party services obviously creates a problem since they respond with error immediately.

Service Code:

[ServiceContract]
public interface IService
{
 [OperationContract]
 [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare,
  RequestFormat = WebMessageFormat.Xml,
  UriTemplate = "")]
 Message SendData(Message message);

}

public class Service : IService
{
 public Message SendData(Message message)
 {   return Message.CreateMessage(MessageVersion.None, String.Empty, "test");
 }
}

Client Code:

public class Client: WebChannelFactory<IService>, IService
{
 public Client(Uri baseUri, string userName, string password)
  : base(CreateBinding(),
      baseUri)
 {
  Credentials.UserName.UserName = userName;
  Credentials.UserName.Password = password;
 }

 public Message SendData(Message requestMessage)
 {
  var channel = CreateChannel();
  Message responseMessage = channel.SendData(requestMessage);
  return responseMessage;
 }

 private static Binding CreateBinding()
 {
  var binding = new WebHttpBinding();
  binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
  binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
  return binding;
 }

}

Using TcpTrace I’m seeing these to requests back to back:

POST / HTTP/1.1  
Content-Type: application/xml; charset=utf-8  
VsDebuggerCausalityData:   uIDPo2lH6p+lUOdFmrqDKGWYeQkAAAAA7+Y4QR6wNUWZmwCaasMx7xrfcJZxph9NocstwCh8NQsACQAA  
Host: localhost:9090  
Content-Length: 89  
Expect: 100-continue  
Connection: Keep-Alive  

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"&gt;test request</string>  

POST / HTTP/1.1  
Content-Type: application/xml; charset=utf-8  
VsDebuggerCausalityData: uIDPo2lH6p+lUOdFmrqDKGWYeQkAAAAA7+Y4QR6wNUWZmwCaasMx7xrfcJZxph9NocstwCh8NQsACQAA  
Authorization: Basic dGVzdDp0ZXN0  
Host: localhost:9090  
Content-Length: 89  
Expect: 100-continue  

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"&gt;test request</string>

Note only second call contains: Authorization: Basic dGVzdDp0ZXN0 How to stop the first request (without authorization) to be made?

Sample solution with TcpTrace utility can be downloaded here:

WCF-BasicAuthenticationIssue.zip

+2  A: 

The problem actually lies with those third parties. Per RFC 2617 Basic authentication is done in two calls, just like Digest. The server should respond at the first call with a challenge containing a realm:

Upon receipt of an unauthorized request for a URI within the
protection space, the origin server MAY respond with a challenge like
the following:

  WWW-Authenticate: Basic realm="WallyWorld"

where "WallyWorld" is the string assigned by the server to identify
the protection space of the Request-URI

The WCF endpoint will pre-authenticate only subsequent calls after the first one. The very first call made by an appdomain to any resource will not contain the basic header user name and password.

See also What happened to the PreAuthenticate flag in WCF.

Remus Rusanu
Ramus,Thank you very much for you answer. This clarifies a lot.
Jakub Borys
+1  A: 

So based on Remus's answer this is my workaround

     public Message SendData(Message requestMessage)
 {
  var channel = CreateChannel();
  Message responseMessage;
  using (new OperationContextScope((IClientChannel)channel))
  {
   WebOperationContext.Current.OutgoingRequest
    .Headers[HttpRequestHeader.Authorization] = "Basic "
    + Convert.ToBase64String(Encoding.ASCII.GetBytes(
    Credentials.UserName.UserName + ":" + Credentials.UserName.Password));
   responseMessage = channel.SendData(requestMessage); 
  }
  return responseMessage;
 }

I'm simply forcing first request to go out with Basic Authorization

Jakub Borys
Yeap, that's what everybody does, add the headers manually.
Remus Rusanu