Windows Authentication in Service Fabric and ASP.NET Core 2.0

Posted on 7 September, 2017

EMEA Business Productivity Consultant

Recently, I worked on a Service Fabric solution for a customer, where my team had to configure secure communication capabilities to existing reliable (stateless) services, built on top of the ASP.NET Core 2.0 framework. More specifically, we had to configure the Windows Authentication feature on them and choose WebListener as the web server, to process HTTP requests from remote Windows clients.

We’ll see that there are slight differences in the names of some ASP.NET packages and libraries, and in the way we configure a weblistener on a stateless service, with the latest version of ASP.NET, with respect to the previous versions (1.x). This article will highlight these aspects and describe a way to properly configure a Service Fabric (SF) Reliable Service Stateless Service, given these requisites.

We will leverage on the features, improvements and support made available by the latest release of Service Fabric SDK for Windows (v5.7.198).

Among the others, I’d like to focus on the following feature:

  • ASP.NET Core 2.0 Support: the Microsoft.ServiceFabric.AspNetCore.* NuGet packages now support ASP.NET Core 2.0, the latest major version of the open-source and cross-platform framework for building modern cloud-ready web applications.

For a fully-documented release notes page, please visit the Azure Service Fabric Team Blog.

In the following section, we’ll be building a simple ASP.NET Core 2.0 application, packaged as Stateless Service, using the Stateless ASP.NET Core project template provided by Visual Studio. We’ll then configure security on the application to perform Windows-authenticated calls.

Some assumptions:

  • Since the sample is built and deployed on a local SF cluster, make sure that the latest of both SF SDK and Runtime are installed on your local machine through the Web Platform Installer, and the cluster is started with the 1-Node / 5-Node configuration
  • The latest .NET Core SDK is installed (v2.0.0)
  • Visual Studio 2017 with support to ASP.NET Core 2.0 is installed (v15.3)

Service Fabric Application

1. Open Visual Studio as Administrator.
2. Create a Service Fabric application, name it MyApplication.

service-fabric-application
3. Create a Stateless ASP.NET Core Service, name it MyAspNetService.

my-asp-net-service
4. Make sure you select ASP.NET Core 2.0 in the following dialog. For this example, I used Empty project template and No Authentication as authentication method (we’ll set it programmatically).

empty

Wait until Visual Studio has set up either the application and the service projects, then navigate to MyAspNetService.cs file, which contains the class representing the SF Stateless Service used for our purposes.

Here’s the signature and the body of the CreateServiceInstanceListeners(…) method, that developers can override to create diverse listeners for this service instance, even custom ones:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext =>
new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");

return new WebHostBuilder()
                                    .UseKestrel()
                                    .ConfigureServices(
                                        services => services    
                                            .AddSingleton<StatelessServiceContext>(serviceContext))
                                    .UseContentRoot(Directory.GetCurrentDirectory())
                                    .UseStartup<Startup>()
                                    .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                                    .UseUrls(url)
                                    .Build();
                    		}
)
)
};
}

As you can see, a communication listener based on Kestrel has already been set up as default listener for the only service endpoint configured (ServiceEndpoint). ASP.NET Core comes with two server implementations, briefly explained below. We chose WebListener as web server, since it supports Windows Authentication.

Kestrel

Kestrel is a cross-platform HTTP server based on libuv library, for asynchronous I/O operations on cross-platform architectures. As previously shown, Kestrel is the web server that is included by default in ASP.NET Core new project templates.

It supports the following features:

  • HTTPS
  • Opaque upgrade used to enable WebSockets
  • Unix sockets for high performance behind Nginx

WebListener

WebListener is a Windows-only HTTP server, based on the Http.Sys kernel mode driver.

WebListener supports the following features:

  • Windows Authentication
  • Port sharing
  • HTTPS with SNI
  • HTTP/2 over TLS (Windows 10)
  • Direct file transmission
  • Response caching
  • WebSockets (Windows 8)
  • Supported Windows versions:
    • Windows 7 and Windows Server 2008 R2 and later

Learn more about WebListener web server implementation in ASP.NET Core.

Modify the method CreateServiceInstanceListeners(…) as below:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext =>
new WebListenerCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");
return new WebHostBuilder()
.UseHttpSys(
options =>
{
options.Authentication.Schemes = AuthenticationSchemes.Negotiate; // Microsoft.AspNetCore.Server.HttpSys
                                                		options.Authentication.AllowAnonymous = false;
                                                		/* Additional options */
                                                		//options.MaxConnections = 100;
                                                		//options.MaxRequestBodySize = 30000000;
                                                		//options.UrlPrefixes.Add("http://localhost:5000");
                                            		}
                                    		)
                                    		.ConfigureServices(
                                        			services => services
                                            			.AddSingleton<StatelessServiceContext>(serviceContext))
                                    		.UseContentRoot(Directory.GetCurrentDirectory())
                                    		.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                                    		.UseUrls(url)
                                    		.Build();
}
)
)
};
}

The following NuGet packages are required to make a successful build:

  • Microsoft.ServiceFabric.AspNetCore.WebListener (v2.7.198)
  • Microsoft.AspNetCore.Server.HttpSys (v2.0.0)

Here’s the using region:

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.ServiceFabric.Services.Communication.AspNetCore;
using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;
using System.Collections.Generic;
using System.Fabric;
using System.IO;

Considerations

  • The packages Microsoft.AspNetCore.Server.WebListener and Microsoft.Net.Http.Server have been merged into the aforementioned new package Microsoft.AspNetCore.Server.HttpSys. The namespaces have been updated to match. This is reflected in calling UseHttpSys() extension method instead of UseWebListener().
  • Windows Authentication is performed by the HttpSys options , by setting:

options.Authentication.Schemes to the enum AuthenticationSchemes.Negotiate

options.Authentication.AllowAnonymous to none.

Learn more about HTTP.sys web server implementation in ASP.NET Core.

  • I provided additional options in the code (commented) regarding:
    • maximum client connections
    • maximum request body size
    • URLs and port configuration options

Publish and test

Once you publish the application, and the service instance(s) is up and running in your local cluster environment, you can simulate HTTP requests towards the endpoint configured in the ServiceManifest.xml of the service project (in my example, http://localhost:8234).

Cluster:

cluster

Snippet from ServiceManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<ServiceManifest Name="MyAspNetServicePkg"
                 Version="1.0.0"
                 xmlns="http://schemas.microsoft.com/2011/01/fabric"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ServiceTypes>
    <!-- This is the name of your ServiceType. 
         This name must match the string used in RegisterServiceType call in Program.cs. -->
    <StatelessServiceType ServiceTypeName="MyAspNetServiceType" />
  </ServiceTypes>

  <!-- Code package is your service executable. -->
  <CodePackage Name="Code" Version="1.0.0">
    <EntryPoint>
      <ExeHost>
        <Program>MyAspNetService.exe</Program>
        <WorkingFolder>CodePackage</WorkingFolder>
      </ExeHost>
    </EntryPoint>
  </CodePackage>

  <!-- Config package is the contents of the Config directoy under PackageRoot that contains an 
       independently-updateable and versioned set of custom configuration settings for your service. -->
  <ConfigPackage Name="Config" Version="1.0.0" />

  <Resources>
    <Endpoints>
      <!-- This endpoint is used by the communication listener to obtain the port on which to 
           listen. Please note that if your service is partitioned, this port is shared with 
           replicas of different partitions that are placed in your code. -->
      <Endpoint Protocol="http" Name="ServiceEndpoint" Type="Input" Port="8234" />
    </Endpoints>
  </Resources>
</ServiceManifest>

You can reach the fully working example on my GitHub repository. -AA