A Recipe for Solid SSL in Sitecore
When building a modern website, a developer needs to pay attention to many things. One of those is security; it always should be placed at the top of the priority list. And the best way to protect site bytes and user input while they’re traveling between a browser and web servers is Transport Layer Security (TLS a.k.a. SSL). If you want to learn how TLS can be implemented in Sitecore, you don’t need to search further. Let’s start…
There are two important rules that need to be followed for every SSL implementation in Sitecore:
- Links to site resources must use the right schema. That means whenever a link to the page, image or any other resource is generated, that link automatically comes with the correct schema (HTTP or HTTPS).
- Resource access protection is key. Only one schema is allowed for every resource, which means all requests with different schema should be rejected or redirected using the correct schema.
Following these two rules will give you the functionality you want without bringing that concern into the business logic code.
What Sitecore gives us out of the box
Out of the box, Sitecore supports two modes: “HTTP for the whole site” and “HTTPS for the whole site.”
Schema selection is controlled by the “scheme” site attribute in the config file. The Sitecore link provider internally respects that attribute and renders the correct URL. That configuration covers the first part of the required implementation we just discussed.
Resource access protection should be offloaded to IIS or Load Balancer or any other Sservice outside of Sitecore. Rules for that protection are defined per site/domain and there is no need to manage them in Sitecore.
These two modes should be enough for the typical site unless the complications described in next section are critical for your implementation. Moreover, “all under HTTPS” will become the preferable option very soon when HTTP/2 finally becomes a reality. Unfortunately, that happy time has not yet arrived as of Spring of 2016, but use HTTP/2 with HTTPS if that’s a viable option for you.
What are the downsides of a “whole site under HTTPS” strategy
Transport Layer Security is great and it brings lots of benefits. It protects your site by ensuring no one can read something that they shouldn’t be able to see. And once your bytes are protected from unauthorized eyes, they’re also protected from being changed by unauthorized entities. That is a second significant benefit. The Internet is full of “smart” devices which may reject traffic they don’t understand, cache something that shouldn’t be cached or even alter your content. Transport Level Security is the best protection from all these “XXX in-the-middle” inconveniences.
So, if SSL is so good, why not use it for every page? A few thoughts:
- Transport Level Encryption delays the first page load. Without it, a page can be delivered with one round trip of latency. A 3-way SSL handshake significantly increases that time. In the worse case scenario, it adds hundreds of milliseconds or even seconds.
- If SSL is used for a page, every resource on that page should be protected as well. Otherwise, it will not be loaded. Unfortunately, it’s still possible to find resources that do not support HTTPS; if you have to use them, it can be an issue. I have seen big sites that were switched from HTTPS to HTTP because of a single IFrame that wasn’t available over HTTPS.
To solve these issues, many sites want to encrypt only pages that contain sensitive data, like forms, and keep HTTP for the rest of the site. How we can achieve that in Sitecore?
Let’s talk about how we can enable SSL only for the specific pages we choose.
First we need to add a flag to every page to show if it should be protected. So, we’ll add the “Require SSL” field to the page template and make that field versioned for multi-lingual differences – if necessary.
The next step is to implement a custom link provider that renders HTTPS links when necessary:
- The provider should respect the “scheme” site attribute.
- It should also use the “Require SSL” page field to figure out what schema should be used for a given page.
Be careful with the alwaysIncludeServerUrl link manager attribute. Your code should always render a full URL if the target link schema is different from the one used for the current page.
The next step is access protection – i.e., protecting a page from being rendered with the wrong schema if requested by a user. Intercept the request in the <httpRequestBegin> pipeline after the ItemResolver processor has been executed. Here you can detect the site, get the page item from the context and check the “Require SSL” field value. You also need to detect the protocol used in the current request. Once you compare the protocol used in the request with the protocol allowed by page, you can return a redirect message if they don’t match.
Now let’s talk about how we can detect the protocol used when the page is requested. Your first approach might be based on the Request.IsSecureConnection property. If you’re sure you will never implement SSL offloading, that’s good enough. Continue reading if you’re not sure about that.
SSL offload does exactly what it is meant to do. Certain devices (like a load balancer) will handle SSL encryption in front of the site so the web server should not care about certificates and traffic encryption/decryption. As a result of that offloading, the site will always get an HTTP request instead of HTTPS. If we don’t cover that in our code, the page will go into an infinite page redirect loop:
- Load Balancer gets HTTPS request for page.
- Load Balancer decrypts it and forwards to the site as HTTP request.
- Site checks required schema for page, sees a mismatch (Request.IsSecureConnection returns false here) and returns redirect to HTTPS response message.
- Browser performs redirect and asks for HTTPS version of the page.
- Now we are back to Step 1…
This is solvable, but it is not as easy as you might think. Unfortunately, there is no single flag that tells the web server that this request originally came as HTTPS and the schema was changed as part of SSL offloading. Every vendor has its own rules; please see this StackOverflow topic for more details. Common practices now for notifying a site include using different ports or adding a custom HTTP header. Feel free to hard code detection logic in your code if you control the offloading process and know how that is configured in your case. Otherwise, you can implement the custom pipeline that will be called for SSL detection. Processors in that pipeline can vary to address different environments:
<configuration xmlns:patch="<a class="external-link" href="http://www.sitecore.net/xmlconfig/" rel="nofollow">http://www.sitecore.net/xmlconfig/"</a>> <sitecore> <pipelines> <score.sslDetection> <processor type="Score.Custom.Pipelines.SslDetection.Detectors.SslDetectionByHeader, Score.Custom"> <HeaderName>Forwarded</HeaderName> <HeaderValue>proto=https</HeaderValue> </processor> </score.sslDetection> </pipelines> </sitecore> </configuration>
Now you know enough to implement robust SSL for your site. Go coding or just use SCORE, which already includes that feature 🙂