Security

Custom Local Domain using HTTPS, Kestrel & ASP.NET Core


Learn how to create a custom domain, (not “localhost”), for your local ASP.NET Core development environment using Kestrel and secure it with HTTPS and a Self-Signed Certificate.

What You’ll Learn

This step by step tutorial will teach you:

  • How to use the host file to configure a custom local domain
  • Create a self-signed certificate for your custom domain
  • Ensure the certificate is a Trusted Root Certificate
  • How to ingest the certificate into Kestrel

Note: This article is geared towards a “Windows” environment, so unfortunately Linux & OSX users may feel a little left out.

Ingredients

If you want to follow along you’ll need:

  • VS Code (or Visual Studio / another text editor)
  • .NET Core 3.1 SDK
  • PowerShell
  • Postman / Curl / Web Browser

Source Code

The source code for this tutorial can be found on here on GitHub.

Usecase

  • We want to use HTTPS in our local development environment.
  • We want to use a “custom” domain, i.e. we don’t want to use the standard “localhost”

But why?

Dotnet dev-certs

Taking a step back, you can very easily, (and probably already have either directly or indirectly), created a default / standard self-signed localhost development certificate using the dotnet dev-certs tool. Running this command:

dotnet dev-certs https --trust

Will add a self-signed certificate to the Windows as shown below:

Localhost Dev Cert

An “out the box” ASP.NET Core application will then use this certificate by default, (well, the  Kestrel web server will), and the site will appear secure when browsed to using HTTPS, as shown below:

Secure Localsite

If that works for you, (and in most cases it will), then you can stop reading here and get on with your life!

However, there are situations where the use of “localhost” as your Domain name doesn’t cut it, and you need to use a “proper” custom domain. The reason for this is that “localhost” can be treated in a unique way by some applications which may mean that your code does not operate as expected, (or more likely the client applications calling your app, don’t work as expected…)

I had this exact issue then preparing another article. The software I was writing about would not route to my .NET Core API that was running on localhost. Moreover as I wanted run this over HTTPS, this added further complexity…

Create a Custom Domain

So in order to get a “custom” local domain, I’m just going to update our local Hosts file. The Hosts file in case you’re wondering is just a simple file with IP Address to Host Name entries that we can use for this exact purpose – it’s simple and quick.

Microsoft TCP/IP Host Name Resolution Order

Just before we update the Hosts file it’s worth understanding the order in which “domain names” are resolved to IP addresses. This article by Microsoft, explains the Host Name Resolution Order on a Windows PC, basically the order in which your PC will attempt to resolve a Domain Name to an IP Address, are:

  1. Check if the name queried is its own
  2. Check the Hosts File (that we’re going to update)
  3. Domain Name System (DNS) Servers
  4. NetBIOS

In most cases the domain name will be resolved by the DNS Servers you are using, ordinarily this will be the DNS Servers that your ISP or Company Network have decided to use. You’ll note though that the Hosts file will be queried before the DNS servers – so just be aware of that. So we can in effect put any domain name in here and it will be resolved before the DNS server entries, but obviously for our PC only, (don’t worry your not going to end up redirecting Google’s traffic to your laptop).

The Hosts file can only be updated by Administrators so to update I’d recommend running a simple Notepad.exe as Admin:

  1. Search for Notepad
  2. Right Click
  3. Select Run As Administrator

As shown below:

Run Notepad as Admin

The from Notepad, open the hosts file from the following location, (if your Windows installation is not on the C: drive then swap out): C:\Windows\System32\drivers\etc

On the Open File Dialog be sure to select “All Files”, then select the Hosts file – it should have an “.ics” extension:

Open Hosts File

Depending on how your PC is set up will depend on the contents of this file, but it should look something like this, (although not exactly for obvious reasons):

Hosts File

You can see I’ve already updated with the IP Address of my PC and the custom domain I want, in this case “localshop.io”

To get your IP Address, open PowerShell or a Command Line and type:

ipconfig

Again depending on how things have been set up will depend on what results you get back, (you’ll probably have a load of “virtual” adapters in addition to the physical adapters you have). As shown below I’ve selected the IP address, (IPv4), for my physical Wireless Network card that’s connecting me to the rest of my home network and the internet:

My IP Address

Save the hosts file, and I always re-open to make sure my changes have taken place. You should now be able to “ping” your chosen custom domain, and it should resolve to the IP address you entered, see my example below:

Successful Ping

If you only want to use HTTP, (and are not using HTTPS), you can stop here and go on your merry way, after you’ve updated the applicationUrl section in the launchSettings.json file to reflect your custom domain name. If you do want to use HTTPS, we have more work to do, but before we move on, a word of caution…

DHCP – Changing IP Address

If like me your using Dynamic Host Control Protocol, (DHCP), to dynamically allocate IP addresses on your network, just be aware that the IP Address you’ve configured in the Hosts file will need to be manually updated anytime your network adapter is assigned a new IP address via DHCP. So once you’re up and running and then one day everything stops working – check the IP address in the Hosts file Vs the IP Address assigned to your network adapter and make sure they are the same. There are ways around this, (e.g. assign yourself a static IP address), but that’s outside the scope of this article.

HTTPS & Certificates

I was originally going to detail a bit about the mechanics of HTTPS and Certificates, but there’s already a load of great info out there that I’d just be duplicating. If you want an overview I highly recommended reading the stuff Cloudflare has put up here, it’s really readable and to the point.

I’d also highly recommend this article by Panayotis Vryonis on Public Key Encryption, it’s one of the best explanations I’ve read, (it’s also referenced by the Cloudflare docs).

 Self-Signed Certificate

So I’ve already mentioned the creation of a self-signed certificate using the dotnet dev-certs tool, however we are now going onto use a different technique to create a new self-signed certificate for our custom domain. To do this we are going to use PowerShell, and specifically the New-SelfSignedCertificate “cmdlet”.

So open a new PowerShell session with Administrator rights, (as we had to do when we edited the hosts file), and enter the following to create a new self-signed certificate, being sure to replace the the domain name with the one you want to use:

$cert = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname localshop.io

This will create a new certificate and place it in Intermediate Certification Authorities. To view it, (although we’re not yet done so don’t close your PowerShell session), type “cert” into the windows search box and select Manage User Certificates:

Open Certificate Manager

Once CertMan is open browse to Intermediate Certification Authorities:

Intermediate Cert Store

Back in the PowerShell session, if you issue:

$cert

You should see some thing like this:

Certificate Print Out

Take note of:

  • The unique thumbprint
  • The correct domain name
  • The capabilities of this certificate, (this certificate can identify both clients and the server)

Next we’re going to create a password that will be used to protect the Certificate, (specifically the Private Key), when we come to using it later, so ensure you keep this safe.

Still in the same PowerShell session, enter:

$pwd = ConvertTo-SecureString -String "pa55w0rd!" -Force -AsPlainText

This will create a secure password string, (feel free to update the “pa55w0rd!” value with something of your own – just remember it!), and places it in a local PowerShell variable called $pwd.

Next we’re going to create another local variable that will contain the path to our certificate, this makes use of the unique certificate thumbprint that we reference via our existing $cert variable:

$certpath = "Cert:\localMachine\my\$($cert.Thumbprint)"

Finally we’re going to export the certificate as a .pfx file that we can then use elsewhere:

Export-PfxCertificate -Cert $certpath -FilePath d:\localshop.pfx -Password $pwd

In this case I’ve just exported the certificate to the root of my D: drive:

Exported PFX

Add To Trusted Root

We’ll be ingesting this certificate into Kestrel, which it will then present it as the secure certificate for our ASP.NET Core site. In order for our client, (which in this case will be the Chrome web browser), to actually trust this certificate, we’ll need to add it to Trusted Root Certification Authorities, (as dotnet dev-certs did with the localhost certificate).

Note: Both Chrome, Edge and Internet Explorer will look in this location to see if the certificate can be trusted, (i.e. that it has come from a trusted authority). Firefox maintains it’s own list of Trusted Authorities so you’d have to perform additional steps, (not shown here), to import the certificate so Firefox clients can trust it.

In CertMan: browse to the Certificates folder under Trusted Root Certification Authorities. Right-click the folder then select: All Tasks -> Import… This will bring up the Certificate Import Wizard:

Certificate Import Wizard

Click Next, browse to the location of the newly created .pfx file, then click Open, (note you may have to change the file type selector on the File Browse dialog):

Cert Import

After selecting the Certificate file, click Next:

File Selected

Type the password in that you used when you created our certificate:

Password Cert

Click Next, and ensure that the correct location is selected for the import, this should be: Trusted Root Certification Authorities as shown below:

Import Location

Click Next, and on the resulting screen, review your selections and if happy click Finish:

Finish Import

You’ll then get a Security Warning challenge, asking if you’re sure you want to do this. Remember we are now going to “trust” any server that presents this certificate to us…

Sec Warning

Assuming you trust yourself, click Yes and the Certificate will be imported into our Trusted Root Certificate Authority folder, double check by ensuring the certificate has been installed here:

Installed Certificate

Configuring Kestrel

In this final section we are going to configure the Kestrel Web server to ingest our new self-signed certificate, which it communicates via HTTPS.

If you already have an existing ASP.NET Core app that you want to use – feel free to do so, however I’m going to create a very simple out the box webapi for the purposes of this article.

Create Test API

At a command prompt, type the following to ensure you have the .NET Core 3.1 SDK installed as you’ll need it to follow along:

dotnet --version

You should see something like:

Dotnet --version

Next, (assuming you have this installed), move to a directory where you want to create your app and enter:

dotnet new webapi -n HttpsAPI

This will go off and create an out the box ASP.NET Core WebApi called “HttpsAPI”, once it’s created, open the resulting project folder, (not surprisingly called HttpsAPI), in VS Code, (or whatever development environment you are using).

Update launchSettings.json

We are going to configure Kestrel in the Program.cs class so we should remove the “applicationUrl” from launchSettings.json as highlighted below:

Remove applicationURL

Remember to save the launchSettings.json file before moving on.

Add User Secrets

When we come to read in our Certificate for Kestrel to use, we need to supply:

  • The Certificate File Path, (which we’ll retrieve from appsettings.Development.json)
  • The password for the Certificate – which we need to keep “secret”

It is this 2nd point that requires the use of User Secrets which allow us to store sensitive information, (like passwords), in a file called secrets.json. This file is unencrypted but is stored in a file-system protected user profile folder on the local development machine. It is therefore only available to you, the developer, (unless you give someone your Windows login – not advised).

To use User Secrets, move over to the .csproj file, (mine is called HttpsAPI.csproj), and insert a pair of <UserSecretsID> tags to the existing <PropertyGroup> tags as shown below:

Empty User Secrets

We now need to generate and insert a GUID value in between these tags, this ensures we retrieve the correct user secrets for a given project. As I’m using VS Code, I’ve installed an extension called “Insert GUID”, which as the name suggests allows you to generate and insert GUIDs into your application code. To insert a GUID using this extension:

  • Install the extension, (search “Insert GUID”)
  • Place your cursor in between the <UserSecretsId> tags
  • Press F1 (in VS Code)
  • Type “Insert Guid” and select it
  • You should then have a choice of GUID formats to insert:

Insert GUID

Pick the 1st Option, and a GUID will be inserted as shown below:

User Secrets

Make sure you save your file before you continue.

If you’re not using VS Code or don’t want to install the Insert GUID extension, there are plenty of free GUID generators online, pick one, then generate and insert a GUID before moving on.

Now open a command prompt “inside” your project, (if you do a directory listing you should see the .csproj file we just updated), and issue the following command to insert a new User Secret:

dotnet user-secrets set "CertPassword" "pa55w0rd!"

Make sure to update the password to whatever you used when you created the Certificate, you should see something like this:

User Secret Added

On Windows the secrets.json file is stored in: C:\Users\<User Name>\AppData\Roaming\Microsoft\UserSecrets\<ProjectGUID>

I’ve shown this location and the opened file below:

Opened User Secrets

So we now have a single user secret with a name of “CertPassword” and a value of “pa55w0rd!”.

Update appsettings.Development.json

The last bit of config we require is to store the file path location of the .pfx file we created earlier on, we’re going to store that in appsettings.Development.json, so add a new JSON attribute called CertificateFileLocation and assign the relevant value, (that corresponds to where the file it located), as shown below, (noting the escaped back-slash in the file path):

"CertificateFileLocation": "d:\\https.pfx"

To put the change in context, your appsettings.Development.json file should look like this:

applicationSettings.json

Update Program.cs

Finally we need to add the code to the Program class to:

  • Read in our 2 config elements (Our Password & Certificate File Path)
  • Configure Kestrel accordingly

I’ve included the code for our Program and simple HostConfig classes below, but remember the code is also available on GitHub.

public class Program
{
  public static void Main(string[] args)
  {
    CreateHostBuilder(args).Build().Run();
  }

  public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
      .ConfigureServices((context, services) =>
      {
        HostConfig.CertificateFileLocation = 
          context.Configuration["CertificateFileLocation"];
        HostConfig.CertificatePassword =
          context.Configuration["CertPassword"];
      })
      .ConfigureWebHostDefaults(webBuilder =>
      {
        webBuilder.ConfigureKestrel(opt =>
        {
          opt.ListenAnyIP(5001, listenOpt =>
          {
            listenOpt.UseHttps(
              HostConfig.CertificateFileLocation, 
              HostConfig.CertificatePassword);
          });
          opt.ListenAnyIP(5000);
        });

        webBuilder.UseStartup<Startup>();
      });
}

public static class HostConfig
{
  public static string CertificateFileLocation { get; set; }
  public static string CertificatePassword { get; set; }
}

To quickly run through the code, I’ve labelled the major sections as shown below:

Code Overview

Section 1: HostConfig

This is just a simple static class that has 2 properties used to store our 2 configuration elements.

Section 2: ConfigureServices

We add the ConfigureServices extension method to enable is to get access to our standard configuration sources, which in this case are:

  • appsettingsDevelopment.json (holds our Certificate File Path)
  • secrets.json (holds our Certificate Password)

You’ll notice that we only reference the name of the configuration elements, (CertificateFileLocation and CertPassword), and not the configuration sources themselves. This is because the .NET Core Configuration sub-layer abstracts, (or aggregates), all of the configuration sources so that we only need to reference the config elements by name.

We then simply assign our read-in elements to the properties of our static class for use in the last section.

Section 3: ConfigureWebHostDefaults

Here we access the ConfigureKestrel extension method and configure our 2 ports for listening. You’ll notice that for our HTTPS Port, (5001), we perform some additional configuration to “UseHttps” and in doing so pass in the Certificate Path and Password.

This means when we serve up anything on port 5001, (our HTTPS Port), our self-signed certificate will be used. Indeed as we are using HttpsRedirection (have a look in the Configure method in the Startup class), we’ll serve everything on HTTPS.

Bring it together

Save everything, and issue a:

dotnet build

To check the build of your code, then:

dotnet run

To run it up.

Move over to Chrome and enter the custom domain name, followed by port 5001 and the controller action for our API, (weatherforecast), and you should be served up a HTTPS Session with a valid Certificate:

Secure Custom Domain

Clicking on the “padlock” icon and take a look at the certificate being used:

Valid Cert

 

Security
Secure a .NET Core API using Bearer Authentication