ASP.NET Core in Action 28 Adding HTTPS to an application

28 Adding HTTPS to an application

This chapter covers

• Encrypting traffic between clients and your app using HTTPS
• Using the HTTPS development certificate for local development
• Configuring Kestrel with a custom HTTPS certificate
• Enforcing HTTPS for your whole app

Web application security is a hot topic at the moment. Practically every week another breach is reported, or confidential details are leaked. It may seem like the situation is hopeless, but the reality is that the vast majority of breaches could have been prevented with the smallest amount of effort.

In chapter 29 we’ll look at a range of common attacks and how to protect against them in your ASP.NET Core app. In this chapter we start by looking at one of the most basic security measures: encrypting the traffic between a client such as a browser and your application.

Without HTTPS encryption, you risk third parties spying on or modifying the requests and responses as they travel over the internet. The risks associated with unencrypted traffic mean that HTTPS is effectively mandatory for production apps these days, and it is heavily encouraged by the makers of modern browsers such as Chrome and Firefox. In section 28.1 you’ll learn more about these risks and some of the approaches you can take to protect your application.

In section 28.2 you’ll see how to get started with HTTPS locally using the ASP.NET Core development certificate. I describe what it is, how to trust it on your application, and what to do if it’s not working as you expect.

The development certificate is great for local work, but in production you’ll need to configure a real, production certificate. I don’t describe the process of obtaining a certificate in section 28.3, as that will vary by provider; instead, I show how to configure Kestrel to use a custom certificate you’ve acquired.

In section 28.4 I describe some of the approaches to enforcing HTTPS in your application. Unfortunately, web browsers still expect apps to be available over HTTP by default, so you typically need to expose your application on both HTTP and HTTPS ports. Nevertheless, there are things you can do to push clients toward the HTTPS endpoint, which are considered security best practices these days.

Before we look at HTTPS in ASP.NET Core specifically, we’ll start by looking at HTTPS in general and why you should use it in all your applications.

28.1 Why do I need HTTPS?

In this section you’ll learn about HTTPS: what it is, and why you need to be aware of it for all your production applications. We’re not going to go into details about the protocol or how certificates work at this point, instead focusing on why you need to use HTTPS. You’ll see two approaches to adding HTTPS to your application: supporting HTTPS directly in your application and using SSL/TLS-offloading with a reverse proxy.

So far in this book, I’ve shown how the user’s browser sends a request across the internet to your app using the HTTP protocol. We haven’t looked too much into the details of that protocol other than to establish that it uses verbs to describe the type of request (such as GET and POST), that it contains headers with metadata about the request, and optionally includes a body payload of data.

By default, HTTP requests are unencrypted; they’re plain-text files being sent over the internet. Anyone on the same network as a user (such as someone using the same public Wi-Fi in a coffee shop) can read the requests and responses sent back and forth. Attackers can even modify the requests or responses as they’re in transit, as shown in figure 28.1.

alt text

Figure 28.1 Unencrypted HTTP requests can be read by users on the same network. Attackers can even intercept the request and response, reading or changing the data. HTTPS requests can’t be read or manipulated by attackers.

Using unencrypted web apps in this way presents both a privacy and a security risk to your users. Attackers could read sensitive details such as passwords and personally identifiable information (PII), they could inject malicious code into your responses to attack users, or they could steal authentication cookies and impersonate the user on your app.

To protect your users, your app should encrypt the traffic between the user’s browser and your app as it travels over the network by using the HTTPS protocol. This is similar to HTTP traffic, but it uses an SSL/TLS certificate to encrypt requests and responses, so attackers cannot read or modify the contents.

DEFINITION Secure Sockets Layer (SSL) is an older standard that facilitates HTTPS. The SSL protocol has been superseded by Transport Layer Security (TLS), so I’ll be using TLS preferentially throughout this chapter. Normally, if you hear someone talking about SSL or SSL certificates, they actually mean TLS. You can find the RFC for the latest version of the TLS protocol at https://www.rfc-editor.org/rfc/rfc8446.

In browsers, you can tell that a site is using HTTPS by the https:// prefix to URLs (notice the s), or sometimes by a padlock, as shown in figure 28.2. Most modern browsers these days deemphasize that a site is using HTTPS, as most sites use HTTPS, and instead highlight when you’re on a site that isn’t using HTTPS, flagging it as insecure.

alt text

Figure 28.2 Encrypted apps using HTTPS and unencrypted apps using HTTP in Edge. Using HTTPS protects your application from being viewed or tampered with by attackers.

The reality is that these days, you should always serve your production websites over HTTPS. The industry is pushing toward HTTPS by default, with most browsers marking HTTP sites as explicitly not secure. Skipping HTTPS will hurt the perception of your app in the long run, so even if you’re not interested in the security benefits, it’s in your best interest to set up HTTPS.

TIP You can find a good cheat sheet for HTTPS by OWASP at http://mng.bz/PzxY. ASP.NET Core takes care of most of the points in this list for you, but there are some important ones in the Application section specifically.

Another reason to support HTTPS is that many browser features are available only when your site is served over HTTPS. Some of these features are JavaScript browser APIs, such as location APIs, microphone APIs, and storage APIs. These are available only over HTTPS to protect users from attackers that could modify insecure HTTP requests. Other features apply to server-side apps too, such as Brotli compression and HTTP/2 support.

TIP For details on how the SSL/TLS protocols work, see chapter 9 of Real-World Cryptography, by David Wong (Manning, 2021), http://mng.bz/zxz1.

To enable HTTPS, you need to obtain and configure a TLS certificate for your server. Unfortunately, although that process is a lot easier than it used to be and is now essentially free thanks to Let’s Encrypt (https://letsencrypt.org), it’s still far from simple in many cases. If you’re setting up a production server, I recommend carefully following the tutorials on the Let’s Encrypt site. It’s easy to get it wrong, so take your time.

TIP If you’re hosting your app in the cloud, most providers will provide one-click TLS certificates so that you don’t have to manage certificates yourself. This is extremely useful, and I highly recommend it for everyone. You don’t even have to host your application in the cloud to take advantage of this. Cloudflare (https://www.cloudflare.com) provides a CDN service that you can add TLS to. You can even use it for free.

As an ASP.NET Core application developer, you can often get away without directly supporting HTTPS in your app by taking advantage of the reverse-proxy architecture, as shown in figure 28.3, in a process called SSL/TLS offloading/termination. This is generally standard in Platform as a Service (PaaS) cloud services, such as Azure App Service.

alt text

Figure 28.3 You have two options when using HTTPS with a reverse proxy: SSL/TLS passthrough and SSL/TLS offloading. In SSL/TLS passthrough, the data is encrypted all the way to your ASP.NET Core app. For SSL/TLS offloading, the reverse proxy handles decrypting the data, so your app doesn’t have to.

With SSL/TLS offloading, instead of your application handling requests using HTTPS directly, your app continues to use HTTP. The reverse proxy is responsible for encrypting and decrypting HTTPS traffic to the browser. This often gives you the best of both worlds: data is encrypted between the user’s browser and the server, but you don’t have to worry about configuring certificates in your application.

NOTE If you’re concerned that the traffic is unencrypted between the reverse proxy and your app, I recommend reading Troy Hunt’s post “CloudFlare, SSL and unhealthy security absolutism”: http://mng.bz/eHCi. It discusses the pros and cons of the problem as it relates to decrypting on the reverse proxy and why you must consider the most likely attacks on your website, in a process called threat modeling.

Depending on the specific infrastructure where you’re hosting your app, SSL/TLS could be offloaded to a dedicated device on your network, a third-party service like Cloudflare, or a reverse proxy (such as Internet Information Services [IIS], NGINX, or HAProxy) running on the same or a different server. Nevertheless, in some situations, you may need to handle SSL/TLS directly in your app:

• If you’re exposing Kestrel to the internet directly, without a reverse proxy—This is a supported approach since ASP.NET Core 3.0, and can give high performance. It is also often the case when you’re developing your app locally.
• If having HTTP between the reverse proxy and your app is not acceptable—While securing traffic inside your network is less critical compared with external traffic, it is undoubtedly more secure to use HTTPS for internal traffic too. This may be a hard requirement for some applications or sectors.
• If you’re using technology that requires HTTPS—Some newer network protocols, such as gRPC and HTTP/2, generally require an end-to-end HTTPS connection.

In each of these scenarios, you’ll need to configure a TLS certificate for your application so Kestrel can receive HTTPS traffic. In section 28.2 you’ll see the easiest way to get started with HTTPS when developing locally, using the ASP.NET Core development certificate.

28.2 Using the ASP.NET Core HTTPS development certificates

Working with HTTPS certificates is easier than it used to be, but unfortunately it can still be a confusing topic, especially if you’re a newcomer to the web. In this section you’ll learn how the .NET software development kit (SDK), Visual Studio, and IIS Express try to improve this experience by handling a lot of the grunt work for you, and what to do when things go wrong.

The first time you run a dotnet command using the .NET SDK, the SDK installs an HTTPS development certificate on your machine. Any ASP.NET Core application you create using the default templates (or for which you don’t explicitly configure certificates) will use this development certificate to handle HTTPS traffic. However, the development certificate is not trusted by default. If you access a site that’s using an untrusted certificate, you’ll get a browser warning, as shown in figure 28.4.

alt text

Figure 28.4 The developer certificate is not trusted by default, so apps serving HTTPS traffic using it will be marked as insecure by browsers. Although you can bypass the warnings if necessary, you should instead update the certificate to be trusted.

A brief primer on certificates and signing
HTTPS uses public key cryptography as part of the data-encryption process. This uses two keys: a public key that anyone can see and a private key that only your server can see. Anything encrypted with the public key can be decrypted only with the private key. That way, a browser can encrypt something with your server’s public key, and only your server can decrypt it. A complete TLS certificate consists of both the public and private parts.

When a browser connects to your app, the server sends the public key part of the TLS certificate. But how does the browser know that it was definitely your server that sent the certificate? To achieve this, your TLS certificate contains additional certificates, including one or more certificates from a third party, a certificate authority (CA). At the end of the certificate chain is the root certificate.

CAs are special trusted entities, and browsers are hardcoded to trust specific root certificates. For the TLS certificate for your app to be trusted, it must contain (or be signed by) a trusted root certificate. Browsers periodically update their internal list of root certificates and revoke root certificates that can no longer be trusted.

When you use the ASP.NET Core development certificate, or if you create your own self-signed certificate, your site’s HTTPS is missing that trusted root certificate. That means browsers won’t trust your certificate and won’t connect to your server by default. To get around this, you need to tell your development machine to explicitly trust the certificate.

In production, you can’t use a development or self-signed certificate, as a user’s browser won’t trust it. Instead, you need to obtain a signed HTTPS certificate from a service like Let’s Encrypt or from a cloud provider like AWS, Azure, or Cloudflare. These certificates are already signed by a trusted CA, so they are automatically trusted by browsers.

To solve these browser warnings, you need to trust the certificate. Trusting a certificate is a sensitive operation; it’s saying “I know this certificate doesn’t look quite right, but ignore that,” so it’s hard to do automatically. If you’re running on Windows or macOS, you can trust the development certificate by running

dotnet dev-certs https --trust

This command trusts the certificate by registering it in the operating system’s certificate store. After you run this command, you should be able to access your websites without seeing any warnings or “not secure” labels, as shown in figure 28.5.

alt text

Figure 28.5 Once the development certificate is trusted, you will no longer see browser warnings about the connection.

TIP You may need to close your browser after trusting the certificate to clear the browser’s cache.

If you’re using Windows, Visual Studio, and IIS Express for development, then you might not need to explicitly trust the development certificate. IIS Express acts as a reverse proxy when you’re developing locally, so it handles the SSL/TLS setup itself. On top of that, Visual Studio should trust the IIS development certificate as part of installation, so you may never see the browser warnings at all.

TIP In macOS, before .NET 7, you would have to retrust the developer certificate repeatedly for every new app. In .NET 7, the process is a lot smoother, so you shouldn’t have to retrust it anything like as often!

Trusting the developer certificate works smoothly in Windows and macOS, in most cases. Unfortunately, trusting the certificate in Linux is a little trickier and depends on the specific flavor of Linux you’re using. On top of that, software in Linux often uses its own certificate store, so you’ll probably need to add the certificate directly to your favorite browser. If you’re using any of the following scenarios, you’ll need to do more work:

• Firefox browser in Windows, macOS, or Linux
• Edge or Chrome browsers in Linux
• API-to-API communication in Linux
• An app running in Windows Subsystem for Linux (WSL)
• Running applications in Docker

Each of these scenarios requires a slightly different approach. In many cases it’s one or two commands, so I suggest following the documentation for your scenario carefully at http://mng.bz/JglK.

TIP If you’ve tried trusting the certificate, and your app is still giving errors, try closing all your browser windows and running dotnet dev-certs https --clean followed by dotnet dev-certs https --trust. Browsers cache certificate trust, so the close and open step is important!

The ASP.NET Core and IIS development certificates make it easy to use Kestrel with HTTPS locally, but those certificates won’t help once you move to production. In the next section I show how to configure Kestrel to use a production TLS certificate.

28.3 Configuring Kestrel with a production HTTPS certificate

Creating a TLS certificate for production is often a laborious process, as it requires proving to a third-party CA that you own the domain you’re creating the certificate for. This is an important step in the trust process and ensures that attackers can’t impersonate your servers. The result of the process is one or more files, which is the HTTPS certificate you need to configure for your app.

TIP The specifics of how to obtain a certificate vary by provider and by your OS platform, so follow your provider’s documentation carefully. The vagaries and complexities of this process are one of the reasons I strongly favor the SSL/TLS-offloading or “one-click” approaches described previously. Those approaches mean my apps don’t need to deal with certificates, and I don’t need to use the approaches described in this section; I delegate that responsibility to another piece of the network, or to the underlying platform.

Once you have a certificate, you need to configure Kestrel to use it to serve HTTPS traffic. In chapter 27 you saw how to set the port your application listens on with the ASPNETCORE_URLS environment variable or via the command line, and you saw that you could provide an HTTPS URL. As you didn’t provide any certificate configuration, Kestrel used the development certificate by default. In production you need to tell Kestrel which certificate to use.

You can configure the certificates Kestrel uses in multiple ways. For a start, you can load the certificate from multiple locations: from a .pfx file, from .pem/.crt and .key files, or from the OS certificate store. You can also use different certificates for different ports, use a different configuration for each URL endpoint you expose, or configure Server Name Indication (SNI). For full details, see the “Replace the default certificate from configuration” section of Microsoft’s “Configure endpoints for the ASP.NET Core Kestrel web server” documentation: http://mng.bz/wvv2.

The following listing shows one possible way to set a custom HTTPS certificate for your production app by configuring the default certificate Kestrel uses for HTTPS connections. You can add the “Kestrel:Certificates:Default” section to your appsettings.json file (or use any other configuration source, as described in chapter 10) to define the .pfx file of the certificate to use. You must also provide the password for accessing the certificate.

Listing 28.1 Configuring the default HTTPS certificate for Kestrel using a .pfx file

{
  “Kestrel”: {             #A
    “Certificates”: {      #A
      “Default”: {         #A
        “Path”: “localhost.pfx”,     #B
        “Password”: “testpassword”   #C
      }
    }
  }
}

❶ Creates a configuration section at Kestrel:Certificates:Default
❷ The relative or absolute path to the certificate
❸ The password for opening the certificate

The preceding example is the simplest way to replace the HTTPS certificate, as it doesn’t require changing any of Kestrel’s defaults. You can use a similar approach to load the HTTPS certificate from the OS certificate store (Windows or macOS), as shown in the “Replace the default certificate from configuration” documentation mentioned previously (http://mng.bz/wvv2).

WARNING Listing 28.1 hardcoded the certificate filename and password for demonstration, but you should never do this in production. Either load these from a configuration store like user-secrets, as you saw in chapter 10, or load the certificate from the local store. Never put production passwords in your appsettings.json files.

All the default ASP.NET Core templates configure your application to serve both HTTP and HTTPS traffic, and with the configuration you’ve seen so far, you can ensure that your application can handle both HTTP and HTTPS in development and in production.

However, whether you use HTTP or HTTPS may depend on the URL users click when they first browse to your app. For example, imagine you have an app that listens using the default ASP.NET Core URLs: http://localhost:5000 for HTTP traffic and https://localhost:5001 for HTTPS traffic. The HTTPS endpoint is available, but if a user doesn’t know that and uses the HTTP URL (the default option in browsers), their traffic is unencrypted. Seeing as you’ve gone to all the trouble to set up HTTPS, it’s probably best that you force users to use it.

28.4 Enforcing HTTPS for your whole app

Enforcing HTTPS across your whole website is practically required these days. Browsers are beginning to explicitly label HTTP pages as insecure; for security reasons, you must use TLS any time you’re transmitting sensitive data across the internet. Additionally, thanks to HTTP/2 (and the upcoming HTTP/3), adding TLS can improve your app’s performance. In this section you’ll learn three techniques for enforcing HTTPS in your application.

TIP HTTP/2 offers many performance improvements over HTTP/1.x, and all modern browsers require HTTPS to enable it. For a great introduction to HTTP/2, see Google’s “Introduction to HTTP/2” at http://mng.bz/9M8j. ASP.NET Core even includes support for HTTP/3, the next version of the protocol! You can read about HTTP/3 at http://mng.bz/qrrJ.

There are multiple approaches to enforcing HTTPS for your application. If you’re using a reverse proxy with SSL/TLS-offloading, it might be handled for you anyway, without your having to worry about it within your apps. If that’s the case, you may be able to disregard some of the steps in this section.

WARNING If you’re building a web API rather than a Razor Pages app, it’s common to reject insecure HTTP requests entirely. You’ll see this approach in section 28.4.3.

One approach to improving the security of your app is to use HTTP security headers. These are HTTP headers sent as part of your HTTP response that tell the browser how it should behave. There are many headers available, most of which restrict the features your app can use in exchange for increased security. In chapter 30 you’ll see how to add your own custom headers to your HTTP responses by creating custom middleware.

TIP Scott Helme has some great guidance on this and other security headers you can add to your site, such as the Content Security Policy (CSP) header. See “Hardening your HTTP response headers” on his website at http://mng.bz/7DDe.

One of these security headers, the HTTP Strict Transport Security (HSTS) header, can help ensure that browsers use HTTPS where it’s available instead of defaulting to HTTP.

28.4.1 Enforcing HTTPS with HTTP Strict Transport Security headers

It’s unfortunate, but by default, browsers load apps over HTTP unless otherwise specified. That means your apps must typically support both HTTP and HTTPS, even if you don’t want to serve any traffic over HTTP, as shown in figure 28.6. On top of that, if the initial request is over HTTP, the browser may end up sending subsequent requests over HTTP too.

alt text

Figure 28.6 When you type in a URL, browsers load the app over HTTP by default. Depending on the links returned by your app or the URLs entered, the browser may make HTTP or HTTPS requests.

One partial mitigation (and a security best practice) is to add HTTP Strict Transport Security headers to your responses.

DEFINITION HTTP Strict Transport Security (HSTS) is a specification (https://www.rfc-editor.org/rfc/rfc6797) for the Strict-Transport-Security header that instructs the browser to use HTTPS for all subsequent requests to your application. The HSTS header can be sent only with responses to HTTPS requests. It is also relevant only for requests originating from a browser; it has no effect on server-to-server communication or on mobile apps.

After a browser receives a valid HSTS header, the browser stops sending HTTP requests to your app and uses only HTTPS instead, as shown in figure 28.7. Even if your app has an http:// link or the user enters http:// in the URL bar of the app, the browser automatically replaces the request with an https:// version.

alt text

Figure 28.7 After a browser sends an HTTPS request, the app returns an HSTS header, instructing the browser to always send requests over HTTPS. The next time the user attempts to make an http:// request, the browser aborts the request and makes an https:// request instead.

TIP You can achieve a similar upgrading of HTTP to HTTPS requests using the Upgrade-Insecure-Requests directive in the Content-Security-Policy (CSP) header. This provides fewer protections than the HSTS header but can be used in combination with it. For more details on this directive and CSP in general, see http://mng.bz/mVV4.

HSTS headers are strongly recommended for production apps. You generally don’t want to enable them for local development, as that would mean you could never run a non-HTTPS app locally. In a similar fashion, you should use HSTS only on sites for which you always intend to use HTTPS, as it’s hard (sometimes impossible) to turn off HTTPS once it’s enforced with HSTS.

ASP.NET Core comes with built-in middleware for setting HSTS headers, which is included in some of the default templates automatically. The following listing shows how you can configure the HSTS headers for your application using the HstsMiddleware in Program.cs.

Listing 28.2 Using HstsMiddleware to add HSTS headers to an application

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddHsts(options =>    #A
{    #A
    options.MaxAge = TimeSpan.FromHours(1);    #A
});    #A

WebApplication app = builder.Build();

if(app.Environment.IsProduction())  #B
{
    app.UseHsts();    #C
}

app.UseStaticFiles();
app.UseRouting();

app.MapRazorPages();

app.Run();

❶ Configures your HSTS header settings and changes the MaxAge from the default of 30 days
❷ You shouldn’t use HSTS in local environments.
❸ Adds the HstsMiddleware

The preceding example shows how to change the MaxAge sent in the HSTS header. It’s a good idea to start with a small value initially. Once you’re sure your app’s HTTPS is functioning correctly, you can increase the age for greater security. A typical value for production deployments is one year.

WARNING Once client browsers have received the HSTS header, browsers will default to using HTTPS for all requests to your application. That means you must commit to always using HTTPS for as long as you set MaxAge. If you disable HTTPS, browsers will not revert to using HTTP until this duration has expired, so your application may be inaccessible until then if you aren’t listening on HTTPS! You can notify the browser that your app no longer supports HSTS by setting MaxAge to 0.

One limitation with the HSTS header is that you must make an initial request over HTTPS before you can receive the header. If the browser makes only HTTP requests, the app never has a chance to send the HSTS header, so the browser never knows to use HTTPS. One potential solution is called HSTS preload.

HSTS preload isn’t part of the HSTS specification, but it’s supported by all modern browsers. Preload bakes your HSTS header into the browser so that the browser knows it should make only HTTPS requests to your site. That removes the “first request” problem entirely, but be aware that HSTS preload commits you to HTTPS forever, as it can’t easily be undone.

Once you’re comfortable with your application’s HTTPS configuration, you can prepare your app for HSTS preload by configuring an HSTS header that

• Has a MaxAge of at least one year, though two years are recommended
• Has the includeSubDomains directive
• Has the preload directive

Listing 28.3 shows how you can configure these directives in your app. The listing also shows how to exclude the domain never-https.com so that if you host your app at this domain, HSTS headers won’t be sent. This can be useful for testing purposes.

Listing 28.3 Configuring the application HSTS header for preload

builder.Services.AddHsts(options =>
{
    options.Preload = true;    #A
    options.IncludeSubDomains = true;    #B
    options.MaxAge = TimeSpan.FromDays(365);    #C
    options.ExcludedHosts.Add("never-https.com");    #D
});

❶ Sends the preload directive
❷ Sends the includeSubDomains directive
❸ You must use a max-age directive of at least one year.
❹ Don’t send the HSTS header in responses to requests for this domain.

Once you’ve prepared your application for HSTS preload, you can submit your app for inclusion in the HSTS preload list that ships with modern browsers. Visit the site https://hstspreload.org, confirm that your application meets the requirements, and submit your domain. If all goes well, your domain will be included in a future release of all modern browsers!

TIP For more details on HSTS and attacks it can mitigate, see Scott Helme’s article “HSTS—The missing link in Transport Layer Security,” at http://mng.bz/5wwa.

HSTS is a great option for forcing users to use HTTPS on your website, and if you can use HSTS preload, you can ensure that modern clients never send requests over HTTP. Nevertheless, HSTS preload can take months to enforce, and you won’t always want to take that approach. In the meantime, if a browser makes an initial request over HTTP, it won’t receive the HSTS header and may stay on HTTP! That’s unfortunate, but you can mitigate the problem by redirecting insecure requests to HTTPS immediately.

28.4.2 Redirecting from HTTP to HTTPS with HTTPS redirection middleware

The HstsMiddleware should always be used in conjunction with middleware that redirects all HTTP requests to HTTPS.

TIP It’s possible to apply HTTPS redirection only to specific parts of your application, such as to specific Razor Pages, but I don’t recommend that, as it’s too easy to open a security hole in your application.

ASP.NET Core comes with HttpsRedirectionMiddleware, which you can use to enforce HTTPS across your whole app. You add it to the middleware pipeline in Program.cs, and it ensures that any requests that pass through it are secure. If an HTTP request reaches the HttpsRedirectionMiddleware, the middleware immediately short-circuits the pipeline with a redirect to the HTTPS version of the request. The browser then repeats the request using HTTPS instead of HTTP, as shown in figure 28.8.

alt text

Figure 28.8 The HttpsRedirectionMiddleware works with the HstsMiddleware to ensure that all requests after the initial request are always sent over HTTPS.

NOTE Even with HSTS and the HTTPS redirection middleware, there is still an inherent weakness: by default, browsers always make an initial insecure request over HTTP to your app. The only way to prevent this is with HSTS preload, which tells browsers to always use HTTPS.

The HttpsRedirectionMiddleware is added in some of the default ASP.NET Core templates. It is typically placed after the error handling and HstsMiddleware, as shown in the following listing. By default, the middleware redirects all HTTP requests to the secure endpoint, using an HTTP 307 Temporary Redirect status code.

Listing 28.4 Using HttpsRedirectionMiddleware to enforce HTTPS for an application

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddHsts(o => options.MaxAge = TimeSpan.FromHours(1));

WebApplication app = builder.Build();

if(app.Environment.IsProduction())
{
    app.UseHsts();
}

app.UseHttpsRedirection();     #A

app.UseStaticFiles();
app.UseRouting();

app.MapRazorPages();

app.Run();

❶ Adds the HttpsRedirectionMiddleware to the pipeline and redirects all HTTP requests to HTTPS

The HttpsRedirectionMiddleware automatically redirects HTTP requests to the first configured HTTPS endpoint for your application. If your application isn’t configured for HTTPS, the middleware won’t redirect and instead logs a warning:

warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
      Failed to determine the https port for redirect.

If you want the middleware to redirect to a different port than Kestrel knows about, you can configure that by setting the ASPNETCORE_HTTPS_PORT environment variable. This is sometimes necessary if you’re using a reverse proxy, and it can be set in alternative ways, as described in Microsoft’s “Enforce HTTPS in ASP.NET Core” documentation: http://mng.bz/6DDA.

SSL/TLS offloading, header forwarding, and detecting secure requests
At the start of section 28.1 I encouraged you to consider terminating HTTPS requests at a reverse proxy. That way, the user uses HTTPS to talk to the reverse proxy, and the reverse proxy talks to your app using HTTP. With this setup, your users are protected, but your app doesn’t have to deal with TLS certificates itself.

For the HttpsRedirectionMiddleware to work correctly, Kestrel needs some way of knowing whether the original request that the reverse proxy received was over HTTP or HTTPS. The reverse proxy communicates to your app over HTTP, so Kestrel can’t figure that out without extra help.

The standard approach used by most reverse proxies (such as IIS, NGINX, and HAProxy) is to add headers to the request before forwarding it to your app. Specifically, a header called X-Forwarded-Proto is added, indicating whether the original request protocol was HTTP or HTTPS.

ASP.NET Core includes ForwardedHeadersMiddleware to look for this header (and others) and update the request accordingly, so your app treats a request that was originally secured by HTTPS as secure for all intents and purposes.

If you’re using IIS with the UseIisIntegration() extension, the header forwarding is handled for you automatically. If you’re using a different reverse proxy, such as NGINX or HAProxy, you can enable the middleware by setting the environment variable ASPNETCORE_FORWARDEDHEADERS_ENABLED=true, as you saw in chapter 27. Alternatively, you can add the middleware to your application manually, as shown in section 27.3.2.

When the reverse proxy forwards a request, the ForwardedHeadersMiddleware looks for the X-Forwarded-Proto header and updates the request details as appropriate. For all subsequent middleware, the request is considered secure. When adding the middleware manually, it’s important that you place ForwardedHeadersMiddleware before the call to UseHsts() or UseHttpsRedirection() so that the forwarded headers are read and the request is marked secure, as appropriate.

Using the HSTS and HTTPS redirection middleware is best practice when you’re building a server-side application such as a Razor Pages app that will always be accessed in the browser. If you’re building an API application. however, a better approach is to not listen for insecure HTTP requests at all!

28.4.3 Rejecting HTTP requests in API applications

Browsers have been adding more and more protections, such as the HSTS header, to try to protect users from using insecure HTTP requests. But not all clients are using a web browser. In this section you’ll learn why API applications should generally disable HTTP entirely.

If you’re building an API application, you often can’t rely on requests coming from a browser. Your API application may primarily serve a client-side framework in the browser, but it may also serve mobile applications or provide an API to other backend services. That means you can’t rely on the protections built into web browsers to use HTTPS for your API apps.

On top of that, even if you know all your users are using a browser, the only way to prevent sending all requests over HTTP is to use HSTS preload, as you saw in section 28.4.2. Sending even one request over HTTP can compromise a user, so the safest approach is to listen only for HTTPS requests, not HTTP requests. This is the best option for API apps.

NOTE It would be safest to take this same approach for your browser apps, but unfortunately, browsers currently default to the HTTP versions of apps by default.

You can disable HTTP requests for your application by setting the URLs for your app to include only https:// requests, using ASPNETCORE_URLS or another approach, as described in chapter 27. Setting

ASPNETCORE_URLS=https://*:5001

would ensure that your app serves only HTTPS requests on port 5001 and won’t handle HTTP connections at all. This protects your clients, as they can’t incorrectly make HTTP requests, and it may even make things simpler on your side, as you don’t need to add the HTTP redirection middleware.

HTTPS is one of the most basic requirements for adding security to your application these days. It can be tricky to set up initially, but once you’re up and running, you can largely forget about it, especially if you’re using SSL/TLS termination at a reverse proxy.

Unfortunately, most other security practices require rather more vigilance to ensure that you don’t accidentally introduce vulnerabilities into your app as it grows and develops. In the next chapter we’ll look at several common attacks, learn how ASP.NET Core protects you, and see a few things you need to watch out for.

28.5 Summary

HTTPS is used to encrypt your app’s data as it travels from the server to the browser and back. This encryption prevents third parties from seeing or modifying it.

HTTPS is virtually mandatory for production apps, as modern browsers like Chrome and Firefox mark non-HTTPS apps as explicitly “not secure.”

In production, you can avoid handling the TLS in your app by using SSL/TLS offloading. This is where a reverse proxy uses HTTPS to talk to the browser, but the traffic is unencrypted between your app and the reverse proxy. The reverse proxy could be on the same or a different server, such as IIS or NGINX, or it could be a third-party service, such as Cloudflare.

You can use the ASP.NET Core developer certificate or the IIS express developer certificate to enable HTTPS during development. This can’t be used for production, but it’s sufficient for testing locally. You must run dotnet dev-certs https --trust when you first install the .NET SDK to trust the certificate.

Kestrel is the default web server in ASP.NET Core. It is responsible for reading and writing data from and to the network, parsing the bytes based on the underlying HTTP and network protocols and converting from raw bytes to .NET objects you can use in your apps.

You can configure an HTTPS certificate for Kestrel in production using the Kestrel:Certificates:Default configuration section. This does not require any code changes to your application; Kestrel automatically loads the certificate when your app starts and uses it to serve HTTPS requests.

You can use the HstsMiddleware to set HSTS headers for your application to ensure that the browser always sends HTTPS requests to your app instead of HTTP requests. HSTS can be enforced only when an initial HTTPS request is made to your app, so it’s best used in conjunction with HTTP to HTTPS redirection.

You can enable HSTS preload for your application to ensure that HTTP requests from browsers are never sent and are always upgraded to HTTPS. You must configure your app as shown in listing 28.3, deploy your app with a TLS certificate, and register your app at the URL https://hstspreload.org. This will schedule your app to be included in browsers’ built-in list of HTTPS only sites.

You can enforce HTTPS for your whole app using the HttpsRedirectionMiddleware. This will redirect any HTTP requests to the HTTPS version of endpoints.

If you’re building an API application, you should avoid exposing your application over HTTP entirely and use only HTTPS. Mobile and other nonbrowser clients don’t have protections such as HSTS, so there’s no safe way to support both HTTP and HTTPS. Disable HTTP for your app by listening only on https:// URLs, such as by setting ASPNETCORE_URLS=https://*:5001.

Leave a Reply

Your email address will not be published. Required fields are marked *