The use of TLS (HTTPS) to encrypt communication between the browser and the server has become an accepted best practice in the software industry. In the past, it was difficult and expensive to maintain the certificates necessary to enable HTTPS on your web application. No longer! Let’s Encrypt issues free certificates for any website through an automated mechanism.

In this tutorial, we’ll look at how to use Let’s Encrypt to provide transport-layer security for a web application built with ASP.NET Core and running on Azure App Service. Once the transport layer is taken care of, we’ll add the Stormpath ASP.NET Core integration for secure user storage and authentication.

To follow this tutorial, you’ll need:

Visual Studio 2015 Update 3 or later

A Stormpath account (you can register here)

An active Azure account

A custom domain name

It’s worth noting that the free service tier on Azure doesn’t allow for custom domain SSL (which is what Let’s Encrypt does), so this solution isn’t completely free. You can sign up for an Azure free trial and get $200 of credit, which covers everything you’ll need to do in this tutorial.

To make it easy to use Let’s Encrypt with Azure, we’ll use the Let’s Encrypt Azure site extension, which has a detailed install guide. I’ll reference this guide later when we set up the extension.

Let’s get started!

Create a new ASP.NET Core application

In Visual Studio, create a new project from the ASP.NET Core Web Application (.NET Core) template.



Next, choose the Web Application template. Make sure that Authentication is set to No Authentication — you’ll add it later with Stormpath.

Although we are going to host the application on Azure, you don’t need to check the box to host the application in the cloud. We’ll set up the deployment to Azure when we’re ready to publish the application.



Once you click OK, Visual Studio will create the project for you. If you run the application right now, it will look like this:



Add Stormpath for auth

With Stormpath you get a secure authentication service built right into your application, without the development overhead, security risks, and maintenance costs that come with building it yourself. To install the Stormpath ASP.NET Core plugin, get the Stormpath.AspNetCore package using NuGet.

Then, update your Startup class to add the Stormpath middleware:

// Add this import at the top of the file using Stormpath.AspNetCore; using Stormpath.Configuration.Abstractions; public void ConfigureServices(IServiceCollection services) { services.AddStormpath(new StormpathConfiguration() { Client = new ClientConfiguration() { ApiKey = new ClientApiKeyConfiguration() { Id = "YOUR_API_KEY_ID", Secret = "YOUR_API_KEY_SECRET" } } }); // Add other services } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // Logging and static file middleware (if applicable) app.UseStormpath(); // MVC or other framework middleware here } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Add this import at the top of the file using Stormpath . AspNetCore ; using Stormpath . Configuration . Abstractions ; public void ConfigureServices ( IServiceCollection services ) { services . AddStormpath ( new StormpathConfiguration ( ) { Client = new ClientConfiguration ( ) { ApiKey = new ClientApiKeyConfiguration ( ) { Id = "YOUR_API_KEY_ID" , Secret = "YOUR_API_KEY_SECRET" } } } ) ; // Add other services } public void Configure ( IApplicationBuilder app , IHostingEnvironment env ) { // Logging and static file middleware (if applicable) app . UseStormpath ( ) ; // MVC or other framework middleware here }

The API key ID and secret strings can be generated by logging into the Stormpath console and clicking Create API Key. The API credentials will be downloaded as a file you can open with a text editor. Copy and paste the ID and secret into your Startup class.

Note: For production applications, we recommend using environment variables instead of hardcoding the API credentials into your application. See the documentation for how to accomplish this.

Adding Stormpath to your ASP.NET Core project automatically adds self-service login and registration functionality (at /login and /register ). You can use the [Authorize] attribute to require an authenticated user for a particular route or action method. Your user identities are automatically stored in Stormpath, no database setup or configuration required!

To learn what else you can do with Stormpath in your ASP.NET Core project, see the quickstart in the Stormpath ASP.NET Core documentation.

Deploy to Azure

Now that we have a basic application with user security, let’s deploy to Azure App Service. App Service is a managed hosting service that makes it easy to deploy applications without having to set up and maintain virtual machines.

Navigate to Build > Publish and select Microsoft Azure App Service as your publishing target.

If you’ve never published to Azure, you’ll be prompted to log in with your Azure credentials. After you authenticate, you’ll see a list of your current Azure App Service resources (if you have any).

Since this is a new project, you’ll need to set up a new Resource Group and App Service instance to host it. Click on the New button to create the required resources.



In the first field, type a name for your application. The name you pick will be the temporary Azure URL of your application (in the form of .azurewebsites.net). Enter a name for the Resource Group, and click New to create a new App Service Plan (the defaults are fine).

Once you have populated all the fields on the dialog, click Create to provision the resources in Azure. When the process is complete, the deployment credentials will be populated for you on the next step of the Publish wizard. Click the Validate Connection button to make sure everything is working.



Clicking Publish will cause Visual Studio to build your project. If there aren’t any compilation errors, your project files will be pushed up to Azure. Go ahead and try it!

You can verify that your application is running by visiting http://yourprojectname.azurewebsites.net in a browser. So far, so good! Now we’ll use Let’s Encrypt to enable secure HTTPS connections to your application.

Set up Let’s Encrypt for TLS

There are a few steps to getting Let’s Encrypt set up with your Azure App Service application:

Upgrade your App Service plan to one that supports Server Name Indication (SNI)

Map a custom domain name to your application

Set up the prerequisites for the Let’s Encrypt extension

Install and configure the Let’s Encrypt extension

We’ll take a look at each step in detail.

Upgrade your App Service plan

Unfortunately, the Free tier doesn’t have support for custom certificates. You’ll need to use the Azure portal to upgrade the App Service plan to Basic (B1) or higher. You can upgrade from App Services > (your application) > Scale up (App Service plan):

Pick a tier and click on Select to upgrade your plan. If you’re using the free Azure trial, the tier cost will come out of the trial credits (so you won’t be charged anything right away).

Map a custom domain to your application

Let’s Encrypt issues a TLS certificate for a specific domain, so you’ll need to have a domain ready. You can buy one through the Azure portal, or at a registrar like Namecheap for $10 or less.

You’ll need to find the IP address of your App Service application, which you can find in the Azure portal at App Services > (your application) > Custom domains:

Using this IP address (and the assigned hostname), create these A and TXT records in the DNS record management tool of your domain registrar:

A * <ip address> A @ <ip address> A www <ip address> TXT * <hostname> TXT @ <hostname> TXT www <hostname> 1 2 3 4 5 6 7 A * < ip address > A @ < ip address > A www < ip address > TXT * < hostname > TXT @ < hostname > TXT www < hostname >

This looks a little different in each registrar. In the Namecheap portal, it looks like this:

Once you’ve added these DNS records, you can add the hostname in the Azure portal by clicking Add hostname:

Pick the A Record type and wait for the validation steps to occur. If the validation isn’t successful, you’ll be prompted to fix the problem. When all the checkmarks are green, click Add hostname to save the custom domain.

It can take some time for the DNS caches across the internet to update (up to 48 hours in some cases). You can check the status of your DNS records using the dig tool, or on the web at digwebinterface.

Set up the prerequisites for Let’s Encrypt

The community-built Let’s Encrypt extension for Azure has a few prerequisites that must be set up. I won’t repeat these steps here because the official wiki covers them well! Jump over to How to install: Create a service principal and follow the instructions.

One thing that tripped me up was in the Grant permissions step: the new service principal account needs to be added to both the App Service instance and the Resource Group it resides in. In both cases, select the resource (App Service or Resource Group) and open the Access control (IAM) subpanel. Click Add and follow the steps to add the service principal account as a Contributor role.

One final note: it can take some time for the service principal permissions to populate. I had to wait almost an hour before I could continue. If you get strange errors later on when you’re configuring the extension, you may need to give it a bit more time.

Install and configure the Azure Let’s Encrypt extension

Now it’s time to install and configure the Azure Let’s Encrypt site extension. Go to your site’s SCM page (https:// .scm.azurewebsites.net), then to Site extensions. On the gallery tab, search for “Let’s Encrypt”. Install the 32bit version and click the Restart Site button.

After your site restarts, press the “Play” button on the extension. If you get a “No route registered for ‘/letsencrypt/'” error, try restarting the site one more time.

The Azure Let’s Encrypt extension page should look like this:

Fill out these fields:

Tenant – found on the More services > Azure Active Directory > Domain names screen (in the form of .onmicrosoft.com)

SubscriptionId – found on the App Service > Overview screen

ClientId – created in the previous step

ClientSecret – created in the preview step

ResourceGroupName – found on the App Service > Overview screen

Check the box to update the application settings. Click Next and give the extension some time to work.

When I first tried to save the settings, I got an error (“’authority’ Uri should have at least one segment in the path…”). If you get this error, the extension wasn’t able to automatically create the required application settings keys. Uou’ll need to manually create these keys (with the values from the fields above):

letsencrypt:Tenant

letsencrypt:SubscriptionId

letsencrypt:ClientId

letsencrypt:ClientSecret

letsencrypt:ResourceGroupName

You can create these keys in App Service > (your application) > Application settings > App settings.

When the keys are set up correctly, you’ll see a new screen after clicking Next. Pick the custom domain you want to use, enter your email address, and click Next.

When I first did this, I got some errors about permissions. It turns out I didn’t have the service principal account added to the Resource Group as a Contributor (see the previous section). Once I did this, and gave the permissions time to propogate, the extension worked fine.

That’s it! When you browse to https://yourcustomdomain.com, you’ll see the certificate from Let’s Encrypt in the address bar:

Notice the expiration date on the certificate? Let’s Encrypt certificates are only good for 90 days before they must be renewed. Fortunately, the Let’s Encrypt extension can take care of the renewal automatically.

Automatic certificate renewal

When you install the extension, it sets up a WebJob that will take care of renewing the certificate every three months. You’ll need to set up a Storage account for the WebJob so it can keep track of when it needs to run.

Set up a storage account

Click Storage accounts on the left panel of the Azure portal, and click the Add button. Use these settings for the new Storage instance:

Deployment model: Resource manager

Account kind: General purpose

Performance: Standard

Replication: RA-GRS

Storage service encryption: Disabled

Resource group: Use existing (pick the group that contains your application)

Location: East US, or wherever you like

It’ll take a minute or two to deploy the storage account. When it shows up in the Storage accounts list, select it and open the Access keys panel. Copy the account name and one of the access key values and create a connection string that follows this format:

DefaultEndpointsProtocol=https;AccountName=<your_storage_account_name>;AccountKey=<your_storage_account_access_key> 1 2 DefaultEndpointsProtocol = https ; AccountName = < your_storage_account_name > ; AccountKey = < your_storage_account_access_key >

Copy the connection string and navigate to App Services > (your application) > Application settings > App settings. Create two new settings called AzureWebJobsStorage and AzureWebJobsDashboard , and paste the connection string in both.

Start the WebJob

Select your application in the App Services list, and restart it for good measure. Then, open the WebJobs subpanel. You should see a “letsencrypt” job in the list:



Select the job and click Start. The status should switch to Running. You can click Logs to view the logs and verify that the Let’s Encrypt renewal task is completing without errors. That’s it! Your certificate will now be renewed indefinitely.

Redirecting traffic to HTTPS

With the Let’s Encrypt certificate installed, your site can be reached via HTTPS. However, it can still be reached by plain old HTTP. Ideally, you’d want to redirect anyone who hits your site over HTTP to be automatically upgraded to HTTPS.

This can be accomplished with a small piece of custom middleware for ASP.NET Core. In your Startup class, place this at the top of the Configure method:

if (env.IsProduction()) { app.Use(async (context, next) => { if (context.Request.IsHttps) { await next(); } else { context.Response.Redirect($"https://{context.Request.Host}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}", true); } }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if ( env . IsProduction ( ) ) { app . Use ( async ( context , next ) = > { if ( context . Request . IsHttps ) { await next ( ) ; } else { context . Response . Redirect ( $ "https://{context.Request.Host}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}" , true ) ; } } ) ; }

Since the Let’s Encrypt certificate won’t be available locally, this middleware is only added to the pipeline if InProduction is true. (It’s possible to install a local certificate for IIS Express to use in development, but that’s a post for another day!)

Re-publish your application to Azure App Service using Visual Studio, and try accessing your site over HTTP. You’ll automatically be redirected to HTTPS. Awesome!

Learn more

With free certificates from Let’s Encrypt, there’s no reason not to enable TLS on your ASP.NET Core web applications. And, Stormpath takes care of the security around user management and authentication/authorization easily. It’s a win/win!

Are you adding Let’s Encrypt to an ASP.NET Core application that isn’t hosted on Azure? I’d love to know what platform you’re using; let me know in the comments below.

If you’re interested in learning more about authentication and user management for .NET, check out these resources: