Here is the challenge: securing a system of WCF services with modern OAuth and OpenIDConnect.
The entire business logic of this solution is handled and served by WCFs. Nothing wrong with it, but the security practices were a bit outdated and not up to standard.
Considering that years of features and logic were coded in those WCFs, the budget was limited and the need to upgrade to a secure and modern system became quickly very urgent, a rewrite of the entire solution was definitely not an option. Therefore, I decided to place all the WCFs end-point behind a ASP.NET Core proxy that leverages the Identity Server 4 framework for authentication.
Another challenge I faced was that the entire solution was in VB and I am mainly a C# developer; additionally this is a VB shop that was a little reluctant in moving to C#. But after long pondering and knowing that in the end VB and C# compile down to the same Microsoft Intermediate Lanaguage (MSIL) we decided to code this proxy in C# and eventually start porting some of these WCF functionality into a VB .NET Standard Class Library that plays well with C# projects anyway.
After doing some research I found several sources of information that I am sharing below. Definitely the most important and inspiring one was this video and blog of Shayne Boyer called ASP.NET Core: Getting clean with SOAP . These other sources might be helpful as well:
– Here is a way to securing existing WCFs and keep open the door for more modern WebApi systems. The problem is that is done with Identity Server 3 (older version): https://leastprivilege.com/2015/07/02/give-your-wcf-security-architecture-a-makeover-with-identityserver3/ Check also the link to Github samples.
– Here is an article on CORS for WCF:
https://blogs.msdn.microsoft.com/carlosfigueira/2012/05/14/implementing-cors-support-in-wcf/
The solution in question has a few different clients: Javascript client (browser dashboard apps), mobile apps and server client. IdentityServer 4 has a configuration for each of these case scenarios (or flows) and their Quickstart samples give you a very good idea on how to implement them.
The key here is the creation of the WCF client that from the ASP.NET Core WebApi calls the WCF services. Shayne uses the “svcutil” (ServiceModel Metadata Utility Tool) to automatically generate the WCF client code. But instead I decided to use this great Visual Studio extension that does just that but makes it easier to instantiate and use a WCF client. The extension is called Microsoft WCF Web Service Reference Provider , once installed you just go to “Connected Services” in your solution explorer, input the endpoint url and the tool creates all the code you need. Also, just recently this tool has been included in Visual Studio 2017 (version 15.5 and above), thus no need to install this extension anymore. One thing I must mention about this tool is that it work only with a SOAP endpoints, if the WCF has other type of endpoints (like REST) the tool will not be able to read the service metadata end will return an error. You can always add a SOAP endpoint to your WCF (hopefully you have access to it).
The extension creates a client that you can instantiate by passing the “EndpointConfiguration” type and use it to call each method exposed by the WCF (by default async). Here is how:
WCFclient client = new WCFclient(EndpointConfiguration.soap); var result = await client.MyMethodAsync(args);
Now, one of the things that we need to make sure to do is to close and dispose of the WCF Client at the end of each request. And here the ASP.NET Core Dependency Injection comes in handy.
The ASP.NET Core DI call the Dispose()
method of the injected service if it implements the IDisposable
interface. Our WCF client created with either the VS extension or the svcutil.exe utility does not implement IDisposable
. So we will wrap the client in a class and implement the interface ourselves. The WCF Client do expose the Close()
and Abort()
methods needed to dispose the service client (see below).
Be careful as the ASP.NET docs note the followings:
// container will create the instance(s) of these types and will dispose them services.AddScoped<Service1>(); services.AddSingleton<Service2>(); // container did not create instance so it will NOT dispose it services.AddSingleton<Service3>(new Service3()); services.AddSingleton(new Service3());
So if the DI creates the instance it will dispose of it (if it implements IDisposable
) but if it doesn’t it will not dispose of it even if it implements IDisposable
. So do not pass in an instantiated object as an argument, just let DI do the magic.
In wrapping the WCF client we want to maintain the possibility of changing the EndpointConfiguration as well as other configurations we might want to pass in in the future (like a different URL to point to if necessary). Thus we inherit from the IWCFClient (the WCF interface implemented by the WCF and its client which is brought in by the VS extension above) and IDisposable
and we pass in the configuration directly into “base” by way of the contructor:
public class WCFClientBySoap : IWCFClient, IDisposable { public WCFClientBySoap() : base(EndpointConfiguration.soap) { } private bool disposedValue = false; protected async virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { try { if (State != System.ServiceModel.CommunicationState.Faulted) { await CloseAsync(); } } finally { if (State != System.ServiceModel.CommunicationState.Closed) { Abort(); } } } disposedValue = true; } } public void Dispose() { Dispose(true); } }
Here is a blog that talk about disposing of a WCF Client WCF Client Closal and Disposal
Now we just need to register this wrapper with the ASP.NET Core DI container. Because we want to be able to change the wrapper in case of different configurations, we register the wrapper as a concrete class of the WCF interface. In Startup.cs we add this line in the ConfigurationServices method:
services.AddScoped<IWCFClient, WCFClientBySoap>();
Finally we create a controller/action endpoint for each WCF endpoint leveraging the injected client:
[Produces("application/json")] [Route("mycontroller")] public class MyControllerController : Controller { private IWCFClient _wcfclient; public MyControllerController(IWCFClient wcfclient) { _wcfclient = wcfclient; } [HttpGet("getMyData")] public async Task<IActionResult> GetMyData() { try { var result = await _wcfclient.getMyDataAsync(); if (result == null) { return BadRequest("Couldn't get Data"); } return new ObjectResult(result); } catch (Exception x) { return BadRequest(x.Message); } } }
And this is all. It just works.