Search code examples
c#.netwcfwcf-securitycorewcf

Upgrading WCF client to CoreWCF breaks authentication


I have a WCF client from .NET Framework 4.8 that I need to port to .NET Core, as using the old one is giving a PlatformNotSupportedException after upgrading the underlying project.

The client is generated from a WSDL file, and adding it as a Connected Service in VS updates the Reference.cs client just fine, with the main changes being the GeneratedCodeAttribute being set to "Microsoft.Tools.ServiceModel.Svcutil", "2.1.0" instead of "System.ServiceModel", "4.0.0.0" (after having installed dotnet-svcutil to the project). It also installs these two packages, which I believe contains the CoreWCF client functionality in newer .NET versions:

  • System.ServiceModel.Http (6.0.0)
  • System.ServiceModel.Security (6.0.0)

I have an integration that is creating one of the generated clients and calls an endpoint afterwards:

public string GetArticle(string articleNumber, string goal)
{
    using var client = CreateArtikelClient();
    return client.getArticle(articleNumber, goal);
}

private ArtikelPortTypeClient CreateArtikelClient()
{
    var client = new ArtikelPortTypeClient(ArtikelPortTypeClient.EndpointConfiguration.ArtikelPort);
    client.ClientCredentials.UserName.UserName = _integrationConfiguration.UserName;
    client.ClientCredentials.UserName.Password = _integrationConfiguration.Password;
    return client;
}

The client is created fine, but calling getArticle causes the following exception:

System.ServiceModel.Security.MessageSecurityException
  HResult=0x80131500
  Message=The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Basic realm="Login please"'.
  Source=System.ServiceModel.Http
  StackTrace:
   at System.ServiceModel.Channels.HttpResponseMessageHelper.ValidateAuthentication()
   at System.ServiceModel.Channels.HttpResponseMessageHelper.<ParseIncomingResponse>d__7.MoveNext()
   at System.ServiceModel.Channels.HttpChannelFactory`1.HttpClientRequestChannel.HttpClientChannelAsyncRequest.<ReceiveReplyAsync>d__18.MoveNext()
   at System.ServiceModel.Channels.RequestChannel.<RequestAsync>d__33.MoveNext()
   at System.ServiceModel.Channels.RequestChannel.<RequestAsyncInternal>d__32.MoveNext()
   at System.Runtime.TaskHelpers.WaitForCompletionNoSpin[TResult](Task`1 task)
   at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(MethodCall methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(MethodInfo targetMethod, Object[] args)
   at generatedProxy_1.getArticle(getArticle3Request )
   // my code calling the generated client

So, setting the username and password this way doesn't seem to be sufficent anymore, despite it working fine in .NET Framework. I have seen other posts about CoreWCF where credentials where set this way, so I don't know what I am missing?


Solution

  • It turns out that the client needs to be constructed slightly differently, with the endpoint URL being passed in directly, and the need to create a binding first.

    private ArtikelPortTypeClient CreateArtikelClient()
    {
        var binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
    
        var client = new ArtikelPortTypeClient(binding, new EndpointAddress(_integrationConfiguration.EndpointAddress));
        client.ClientCredentials.UserName.UserName = _integrationConfiguration.UserName;
        client.ClientCredentials.UserName.Password = _integrationConfiguration.Password;
        return client;
    }