Search code examples
c#soapchannelsoapheader

Adding an Authorization header to a WCF scaffolded SOAP web service


Disclaimer: I have not worked with SOAP web services ever. At all. Not even a little bit. So the concept of channels and WCF scaffolding has got me a bit confused, hence I'm here.

I have been asked to integrate to a SOAP XML web service which uses basic authentication. (e.g. Authorization header, Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx <- which is a Base64 encoded username:password). My project is in .NET Core using C#.

I have used Visual Studio WCF connected service discovery to produce scaffolding code which has served me very well for instantiating the required objects etc, however my issue is I've been asked to use Basic authentication, and I have no idea where to inject this code into the scaffolding that's been produced. I have worked with basic authentication before, so I understand 'how' to do it, for things like REST APIs etc. Just username:password, base64 encode and add to Authorization header. However, I am unsure how to do this for this scaffolded SOAP web service.

The code that i believe can be injected into every request, to add your custom headers, is:

        using (OperationContextScope scope = new OperationContextScope(IContextChannel or OperationContext)
        {
            OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = new HttpRequestMessageProperty()
            {
                Headers =
                {
                    { "MyCustomHeader", Environment.UserName },
                    { HttpRequestHeader.UserAgent, "My Custom Agent"}
                }
            };
        // perform proxy operations... 
    }

The OperationContextScope expects either an IContextChannel or OperationContext. I am stuck as to what to add here. If I look at my scaffolded code, I can find the 'client' for the web service, here:

public partial class POSUserInformationManagerV1_2Client : System.ServiceModel.ClientBase<McaEndpointPosUserInformation.POSUserInformationManagerV1_2>, McaEndpointPosUserInformation.POSUserInformationManagerV1_2

And I can find the 'channel' here, but it's just another interface, that doesn't have any contracts specified?

[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
public interface POSUserInformationManagerV1_2Channel : McaEndpointPosUserInformation.POSUserInformationManagerV1_2, System.ServiceModel.IClientChannel
{
}

I looked up ChannelBase, and it seems like it should accept a variety of objects that implement one or another channel interface (including IClientChannel, which the scaffolded POSUserInformationManagerV1_2Channel uses)

    protected class ChannelBase<T> : IDisposable, IChannel, ICommunicationObject, IOutputChannel, IRequestChannel, IClientChannel, IContextChannel, IExtensibleObject<IContextChannel> where T : class
    {
        protected ChannelBase(ClientBase<T> client);

        [SecuritySafeCritical]
        protected IAsyncResult BeginInvoke(string methodName, object[] args, AsyncCallback callback, object state);
        [SecuritySafeCritical]
        protected object EndInvoke(string methodName, object[] args, IAsyncResult result);

But I'm still stuck on what I can put into the OperationContextScope to connect it appropriately to the 'channel'. I've tried POSUserInformationManagerV1_2Client and the relevent Channel interface, but neither will convert to an IContextChannel. Does anyone have any ideas/thoughts?

EDIT: Here is where I am trying to inject the code to add the Auth HTTP header:

    public System.Threading.Tasks.Task<McaEndpointPosUserInformation.requestUserInformationResponse> requestUserInformationAsync(McaEndpointPosUserInformation.requestUserInformation request)
    {
        using (OperationContextScope scope = new OperationContextScope(request)
        {
            OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = new HttpRequestMessageProperty()
            {
                Headers =
                {
                    { "MyCustomHeader", Environment.UserName },
                    { HttpRequestHeader.UserAgent, "My Custom Agent"}
                }
            };
        // perform proxy operations... 
    }
        return base.Channel.requestUserInformationAsync(request);
    }

Solution

  • The issue turned out to be not setting up the transport security to be 'Basic' through the use of:

                    // Set the binding. Without this, the WCF call will be made as anonymous
                    var binding = new BasicHttpsBinding();
                    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;