ASP.NET Core, MVC

From WCF to Secure ASP.NET Core

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 SOAPThese 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.

 

ASP.NET Core

ASP.NET Core 2 is here! Semi painless upgrade.

On August 14, 2017 the ASP.NET team announced the release of ASP.NET Core 2 (along with .NET Standard 2 and .NET Core 2).

As you might have noticed everybody jumped in and upgraded their projects or at least their pet/test projects to v2.0; naturally, I felt the same urge and I did so for my current project.  So this post is to chronicle my fairly painless upgrade with a little hiccup mainly due to the limited documentation.  Keep in mind that Core 2.0 is still in preview even though is a final preview and quite stable.

You can find all sorts of blogs and instruction on how to upgrade, like here or here  and here.  Following all this information I worked my way to 2.0 as follows.

Keep in mind that this is “my” journey and yours might be different. I updated a Web application on a Windows machine with Visual Studio 2017 Community edition.

1. Install .NET Core 2.0 SDK

You can find it here.  The process is very simple just follow the installer instructions.

2. Install Visual Studio 2017 preview version 15.3

You can find it here, actually it wasn’t as clear to me that this was still a preview version and I spent quite a bit of time trying to update my current version of VS 2017 to 15.3 without success (obviously!).  So, Core 2.0 is available with VS2017 in preview only.

Go ahead and install it, it can live side-by-side with your stable version of VS with no problems.

This new version of Visual Studio can automatically detect if a new SDK is available (you just install it in step 1) and will offer you the ability to target the new framework.  You can read more about it here.

3. Install the Microsoft.AspNetCore.All meta package

You can do so by way of Nuget with the dependency management tool offered by VS 2017:

image

Your project probably have several AspNet Core packages but after installing Microsoft.AspNetCore.All you can remove most of them as they are already included in the AspNet.All meta package.  You can find a list of the included packages here.

4. Target .Net Core 2 in all the projects in your solution

This is pretty easy, right click on the project and select properties, in the application dialog select ASP.NET Core 2 as target. This is for the web projects, if you have any other data or business project to support the web app, they should target .NET Standard 2.0:

Target Core 2.0.NET Standard 2.0

At this point all is set, rebuild the project and run it….. but there is always something not exactly right.  Here is what I found:

Content Error

After the steps taken above the first error was the following:

Duplicate ‘Content’ items were included. The .NET SDK includes ‘Content’ items from your project directory by default. You can either remove these items from your project file, or set the ‘EnableDefaultContentItems’ property to ‘false’ if you want to explicitly include them in your project file. For more information, see https://aka.ms/sdkimplicititems. The duplicate items were: ‘wwwroot\img\anonimus.png’

So I checked the .csproj file and it was listing several files and folders part of the content directory (wwwroot):

I thought that this was odd.  The project was created with VS 2017 which automatically detects files in your project folder, so there is no need to actually list them in the .csproj file.  Anyway, I deleted those entry, and the error above was gone.  Great!  The first one is done.

Identity Error (Cookies)

The next series of errors were all due to the cookies settings I had under Identity.  Here is what the error looked like:

IdentityCookies Error
Cookies does not exist under IdentityOptions

I spent quite a bit of time to figure this out.  Everything pointed to this article in the docs, but as the title says, it let you configure cookies in the case where you do not have Identity set up, instead I did have Identity set up.  So I went into the docs to see how to set up Identity with cookies, but that is old code that doesn’t work anymore with Core 2.0.  In fact the previous article about cookies without Identity is explicitly saying that cookies have been removed from Identity.

Finally this Stackoverflow answer pointed me to this Github announcement  from the ASP.NET team which in turns pointed me in the right direction here.  And finally I found the solution.  You can read the details in the post linked above, but now cookies has its own configuration extension to be placed in the ConfigureService method of the Startup class:

services.ConfigureApplicationCookie(conf =>
{
  conf.LoginPath = "/Auth/Login";
  conf.Events = new CookieAuthenticationEvents()
  {
    OnRedirectToLogin = async ctx =>
    {
      if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
      {
        ctx.Response.StatusCode = 401;
      }
      else
      {
        ctx.Response.Redirect(ctx.RedirectUri);
      }
      await Task.Yield();
    }
  };
});

For the rest of the Identity configuration (pasword, etc.) I moved it under Configue<IdentityService>:

services.Configure<IdentityOptions>(config =>
{
  config.User.RequireUniqueEmail = true;
  config.Password.RequiredLength = 8;
});

Now all works perfectly as before.

One last piece of the puzzle is UseIdentity()  in the Configure() method which is deprecated, as it was just calling UseCookie 4 times (as noted on the ASP.NET team post). Instead use UseAuthentication()  here is my Startup class (I removed unrelated code):

public class Startup
    {
        //Removed for brevity

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddIdentity<CCMUser, IdentityRole>()
            .AddEntityFrameworkStores<CCMContext>()
            .AddDefaultTokenProviders();

            services.AddMvc()
            .AddJsonOptions(opt =>
            {
                opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });

            services.ConfigureApplicationCookie(conf =>
            {
                conf.LoginPath = "/Auth/Login";
                conf.Events = new CookieAuthenticationEvents()
                {
                    OnRedirectToLogin = async ctx =>
                    {
                        if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
                        {
                            ctx.Response.StatusCode = 401;
                        }
                        else
                        {
                            ctx.Response.Redirect(ctx.RedirectUri);
                        }
                        await Task.Yield();
                    }
                };
            });

            services.Configure<IdentityOptions>(config =>
            {
                config.User.RequireUniqueEmail = true;
                config.Password.RequiredLength = 8;
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, CCMSeedData seeder, IHttpContextAccessor httpContext)
        {
            //Removed for brevity

            app.UseStaticFiles();

            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");

                routes.MapSpaFallbackRoute(
                    name: "spa-fallback",
                    defaults: new { controller = "Home", action = "Index" });
            });
        }
    }
}

And this is it. I hope this helps someone else like me that had a bit of hard times fixing the cookies setting issue with Identity.

MVC

Return URL with fragment in ASP.NET Core MVC

I am working on a web project based on Asp.Net MVC and Aurelia and I decided to structure it with part MVC and part Spa (Aurelia). The project also implements a basic authentication system (Asp.Net Core Identity) where if a non-authenticated user is trying to access a secure page it is redirected to the login page, which is standard procedure in these cases.

As you can see the return URL sent in with the query string includes a fragment, used by the Aurelia routing (or any spa framework routing you are using):

http://localhost:14500/Auth/Login?ReturnUrl=%2Fcamp%2F7#sessions

The problem is that the fragment  portion of the URL is never sent to the server and it is therefore ignored. So here is what happens:

The fragment of the URL is not being added to the action url of the form, so when you post the login form the server redirects to a URL without fragment and you are not able to get to the correct page handled by the spa router.

So, I included a little Javascript functions that picks up the return URL from the browser’s location.href and updates the form action with the correct return URL:

$(document).ready(function () {
        var form = $("form")[0];
        var hash = document.location.hash;
        if (hash) {
            if (form) {
                if (form.action) {
                    form.action = document.location.href;
                }
            }
        }
    });

Here is how the form action looks like after this code runs:

Now, when the user logs in and the server redirect to the originally requested URL, Aurelia router picks it up and renders the correct page.