Sunday, 22 May 2011

Framework 4.0 WCF basic authentication sends 2 requests

Hi Guys, If you configured your end-point to use basic authentication you probably saw 2 requests coming to server.
Just open fiddler and you will see something like this:








and here is the second request, which how it suppose to be:











It means that Authorization header has not been passed first time.
There are 2 options to resolve it.

Option Number 1
You can use OperationContextScope like shown below.


System.ServiceModel.Channels.HttpRequestMessageProperty httpRequestProperty = new System.ServiceModel.Channels.HttpRequestMessageProperty();
httpRequestProperty.Headers[System.Net.HttpRequestHeader.Authorization] = "Basic " +
    Convert.ToBase64String(Encoding.ASCII.GetBytes(svcClient.ClientCredentials.UserName.UserName + ":" +
    svcClient.ClientCredentials.UserName.Password));
using (System.ServiceModel.OperationContextScope scope = new System.ServiceModel.OperationContextScope(svc.Service.InnerChannel))
{
    System.ServiceModel.OperationContext.Current.OutgoingMessageProperties[System.ServiceModel.Channels.HttpRequestMessageProperty.Name] = httpRequestProperty;
    //Make service call.....
    return svcClient.IsEmailAddressExist(emailAddress);
}

The benefit of using this method - you have control on operation level, so if you service has 20 operations and you need apply this behavior to one one - you can do it as shown above.  But, there is bad thing about it - if your end-point will change binding settings, like basic authentication will be removerd, you have to update your source code. I would recommend this approach only if your service and-point is configured with basic authentication till end of its days :-) nobody can guarantee it.


Option Number 2

If we started talk about flexible end-point configuration without touching source code, this method will be more suitable - Use custom end-point behavior and message inspector.
The benefit of this method that you can easily turn it on and off via application configuration without code recomplication.

First of all, you have to create custom message inspector, which will be responsible for custom header injection like shown below. in Framework version lower than 4.0 I would use IProxyMessageInspector, but this sample is based on Framework 4.0 and I am using IClientMessageInspector.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
 
namespace ServiceDataProviders
{
    /// <summary>
    /// current message inspector injects "Authorization" header, which prevents double server calls
    /// with 401 and 200 response codes.
    /// </summary>
    public class CustomProxyHeaderMessageInspector : IClientMessageInspector
    {
 
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            //throw new NotImplementedException();
        }
 
        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            try
            {
                HttpRequestMessageProperty httpRequest;
                if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name))
                {
                    httpRequest = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
                }
                else
                {
                    httpRequest = new HttpRequestMessageProperty();
                    request.Properties.Add(HttpRequestMessageProperty.Name, httpRequest);
                }
                if (httpRequest != null)
                {
                    httpRequest.Headers.Add(System.Net.HttpRequestHeader.Authorization, 
                        "Basic " +
                        Convert.ToBase64String(Encoding.ASCII.GetBytes(
                        System.Configuration.ConfigurationManager.AppSettings["SVCUserName"] 
                        + ":" +
                        System.Configuration.ConfigurationManager.AppSettings["SVCUserPassword"]
                        ))
                        );
                }
            }
            catch (Exception ex)
            {
                //TODO: add logging for the CustomProxyHeaderMessageInspector
            }
            return request;
        }
    }
}

As you can see, I am using application configuration settings to assign use name and password:
SVCUserName and SVCUserPassword. It can be anything in your case. 

Second - you will have to create end-point behavior, which will register message inspector at end-point level. It means that all operations calls will be affected by that.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
 
namespace ServiceDataProviders
{
    /// <summary>
    /// current class registers CustomProxyHeaderMessageInspector, which injects Authhorization header
    /// </summary>
    public class CustomEndpointCallBehavior : System.ServiceModel.Configuration.BehaviorExtensionElementIEndpointBehavior
    {
        #region BehaviorExtensionElement
        public override Type BehaviorType
        {
            get { return typeof(CustomEndpointCallBehavior); }
        }
 
        protected override object CreateBehavior()
        {
            return new CustomEndpointCallBehavior();
        }
 
        #endregion BehaviorExtensionElement
 
        #region IEndpointBehavior
 
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            //throw new NotImplementedException();
        }
 
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            //throw new NotImplementedException();
            clientRuntime.MessageInspectors.Add(new CustomProxyHeaderMessageInspector());
        }
 
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            //throw new NotImplementedException();
        }
 
        public void Validate(ServiceEndpoint endpoint)
        {
            //throw new NotImplementedException();
        }
        #endregion IEndpointBehavior
    }
}

Current service and point behavior implements IEndpointBehavior in older version of frawework, I would use  IChannelBehavior. IEndpointBehavior implements main logic to register message inspector. You don't have to implement BehaviorExtensionElement if you do not have plans to use application configuration and add behavior dynamically during run-time, but if you want make it configurable via config file you have to do it.

Here is a sample how you can add behavior during run-time:

svcClient.Endpoint.Behaviors.Add(new CustomEndpointCallBehavior());

Here are instructions how to add custom behavior in application configuration:

inside
<system.serviceModel>
you should add following:

 

if you run your code now you should see only one request, coming to server!!! Good luck!
    <behaviors>
      <endpointBehaviors>
        <behavior name="SVCBehavior">
          <HttpAuthHeaderBehavior/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <extensions>
      <behaviorExtensions>
        <add name="HttpAuthHeaderBehavior" type="ServiceDataProviders.CustomEndpointCallBehavior, MyDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </behaviorExtensions>
    </extensions>

plus you have to add behaviorConfiguration attribute to your end-point configuration. here is a sample

      <endpoint address="http://127.0.0.1/CustomerProfile" binding="basicHttpBinding"
        bindingConfiguration="SVCBinding" contract="serviceCustomerProfile.CustomerProfilePortType"
        behaviorConfiguration="SVCBehavior"
        name="CustomerProfilePort" />
After you make all these changes to your project you should see in a fiddler only one request to server. Good luck!

1 comment:

  1. Can you send me the video tutorial of this problem ?
    Please hel me

    ReplyDelete