-1

I am trying to call a .NET Framework WCF Service (which is secured by Windows Authentication using an AD group) from a .NET Core 3.1 API however I am getting the error message:

System.ServiceModel.Security.MessageSecurityException: The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate, NTLM'.

The .NET Core API is hosted in IIS both on windows and the app pool that it runs under has a domain account which is in the AD Group required for access. We currently have other .NET Framework applications calling the WCF service and they all work however this is the first .NET Core application to call it. Both servers which the API is deployed to and the WCF service is deployed to exist on the same domain that support Kerberos protocol.

It works successfully when running locally however when deployed onto a server it gives the above error message.

IIS Logs from the error message occuring:

POST /Broadcast.svc - 8081 - 172.27.19.200 - - 401 2 5 0
POST /Broadcast.svc - 8081 - 172.27.19.200 - - 401 1 3221225581 0

This is the client proxy creation code in the API:

    public IWcfClient<IBroadcastService> CreateBroadcastService()
    {
        var binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
        binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Windows;

        var client = new WcfClient<IBroadcastService>(
            binding,
            new EndpointAddress($"{remoteUrl}/Broadcast.svc"));

        //My expectation is that the below line would make the call send the AppPoolIdentity Credentials?
        client.ClientCredentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;

        return client;
    }

WcfClient.cs (Wrapper for ClientBase):

public class WcfClient<TChannel> : ClientBase<TChannel>, IWcfClient<TChannel> where TChannel : class
{
    public WcfClient(Binding binding, EndpointAddress endpointAddress)
        : base(binding, endpointAddress)
    { }

    /// <summary>
    /// Executes a given action against <see cref="TChannel" />.
    /// </summary>
    /// <param name="invokeAction">The invocation action.</param>
    public void Invoke(Action<TChannel> invokeAction)
    {
        try
        {
            invokeAction(Channel);
            Close();
        }
        catch (CommunicationException)
        {
            Abort();
            throw;
        }
        catch (TimeoutException)
        {
            Abort();
            throw;
        }
    }

    /// <summary>
    /// Executes the given action against <see cref="TChannel" /> and returns the result.
    /// </summary>
    /// <typeparam name="TResult">The type of the result.</typeparam>
    /// <param name="invokeFunc">The invocation function.</param>
    /// <returns>An instance of <see cref="TResult" /></returns>
    public TResult Invoke<TResult>(Func<TChannel, TResult> invokeFunc)
    {
        TResult result;

        try
        {
            result = invokeFunc(Channel);
            Close();
        }
        catch (CommunicationException)
        {
            Abort();
            throw;
        }
        catch (TimeoutException)
        {
            Abort();
            throw;
        }

        return result;
    }
}

Startup.cs Configure method for API:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        logger.Information("Configuring application middleware...");

        if (env.IsDevelopment())
            app.UseDeveloperExceptionPage();

        app.UseSwaggerMiddleware();

        app.UseSerilogRequestLogging();

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

        ConfigCache.SetRootDirectory(Path.Combine(env.ContentRootPath, "App_Data"));

        logger.Information("Application middleware configured successfully.");
    }

Program.cs for API:

public class Program
{
    [UsedImplicitly]
    public static void Main(string[] args)
    {
        var appConfig = new ConfigurationBuilder()
            // ReSharper disable once StringLiteralTypo
            .AddJsonFile("appsettings.json")
            .Build();

        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(appConfig)
            .Enrich.FromLogContext()
            .CreateLogger();

        CreateHostBuilder(args).Build().Run();
    }

    [UsedImplicitly]
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(
                webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                    webBuilder.UseIIS();
                    webBuilder.UseSerilog();
                });
}

The web.config for the .NET Framework WCF service web.config has the specified role in like so (I have removed actual name)

<system.web>
    <authentication mode="Windows"/>
    <authorization>
      <allow roles="DOMAIN\GROUPNAME"/>
      <deny users="*"/>
    </authorization>
</system.web>

Can anyone tell me if I have missed anything or provide any ideas on how to narrow down the problem? Also please comment if you need to see any other areas of the code and will be happy to supply them.

Emcrank
  • 310
  • 2
  • 12
  • I may be off here..but...you may want to review this article about a dotnet CORE app talking to sql server with kerberos (which is linked to AD). You should mention if you're running your dotnet CORE app on windows or on linux. But this is at least a hint to some differences with dotnet CORE. https://www.codeproject.com/Articles/1272546/Authenticate-NET-Core-Client-of-SQL-Server-with-In – granadaCoder Jun 12 '20 at 12:38
  • Thanks, ive updated my question, i found the answer was to disable the loop back security check @granadaCoder – Emcrank Jun 12 '20 at 12:41
  • Are you deploying the dotnetCore code to IIS? Then, yeah, then you're back to more dotnetFW thinking with security. – granadaCoder Jun 12 '20 at 12:44

2 Answers2

0

The fact they are both are hosted on the same machine, you may need to populate the BackConnectionHostNames registry key to disable the loopback security functionality.

Steps here: https://stackoverflow.com/a/48086033/4813939

Emcrank
  • 310
  • 2
  • 12
-1

I see that you use windows authentication. When you use windows authentication, the server should also exist in a Windows domain that uses the Kerberos protocol as its domain controller. If the server is not on a Kerberos supported domain, or if the Kerberos system fails, you can use NT LAN Manager.

        <bindings>
            <basicHttpBinding>
                <binding name="SecurityByTransport">
                    <security mode="Transport">
                        <transport clientCredentialType="Ntlm"/>
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>

For more information about Transport Security,Please refer to the following link:

https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/transport-security-overview

Ding Peng
  • 3,702
  • 1
  • 5
  • 8
  • Both servers which the API is deployed to and the WCF service is deployed to exist on the same domain that support Kerberos protocol. – Emcrank Jun 12 '20 at 08:18
  • If i change it to NTLM, i get the following error: "The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'Negotiate, NTLM'." – Emcrank Jun 12 '20 at 08:19