Securing your ExpressionEngine website with HTTPS

1st August, 2011

Something we have needed to do quite frequently when developing e-commerce sites for ExpressionEngine is securing web pages over SSL. With ExpressionEngine this is fairly simple to implement, but there are a couple of points to watch out for.

Getting SSL set up on your server

First up, you are going to need an SSL certificate, and a dedicated IP address for your website. A dedicated IP address is required, because HTTPS connections are secured before your browser tells the server which domain the request is for. Essentially, a regular HTTP request goes like this:

  1. Browser: Hey, can I have a connection to port 80?
  2. Server: Yip, go ahead.
  3. Browser: Ok, give me exp-resso.com, page /
  4. Server: Sure, here you go!

With an SSL connection, the request goes like this:

  1. Browser: Hey, can I have a secure connection to port 443?
  2. Server: Yip, here’s my certificate.
    (browser checks certificate)
  3. Browser: Ok, give me exp-resso.com, page /
  4. Server: Sure, here you go!

Notice that the certificate is validated before the browser sends the “host” header. The clever folk in charge of SSL realised this, and developed TLS (the replacement for SSL), which includes a technology called Server Name Indication. This allows the browser to specify a host name before the certificate is requested. Unfortunately, this isn’t supported at all on Windows XP, so for the time being at least, you will need a dedicated IP address to support your HTTPS website.

Once you have SSL set up and working on your server, you should be able to browse to https://www.example.com and see your website. If this doesn’t work, you probably need to get in touch with your web host to figure out why things aren’t working.

Securing specific sections of your web site

In some circumstances, you will want to secure your entire website, and only allow it to be served over HTTPS. If this is the case, then you’re nearly done. All you need to do is prevent access over regular HTTP. This can be accomplished using a couple of lines at the top of your .htaccess file:

RewriteEngine On
RewriteCond 
%{HTTPS} off
RewriteRule 
(.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] 

This tells your Apache server “If HTTPS is off, redirect to https://current-domain/current-page”. If you are running your entire site over HTTPS, you should also check that your Site URL and all other URLs in your ExpressionEngine settings use HTTPS. The easiest way to do this is to grab a copy of our free REElocate find-and-replace module to update your settings all at once. 

In most cases however, you don’t want to secure your entire website. This is mainly for performance - HTTPS pages use more server resources, can’t be cached by ISP proxies, which means greater load on your server. It also means pages will load slightly slower for your visitors - which is never a good thing! For this reason, you probably only want to secure certain sections of your website - such as your login/registration pages, “My Account”, and your checkout. If you have gone to the trouble of setting up HTTPS, it’s worth using it for your login/registration pages, because over standard HTTP, it is possible for anyone on the same network (think public wifi) to see everything going back and forth between visitors and your server, including login/passwords.

There are two things to think about when setting up “secure-only” sections of your site: making your your internal links point to HTTPS for these pages, and then ensuring users can’t access the pages over standard HTTP (for security). Using the standard EE path=example variables will render HTTP links, so you will probably want to just hard-code the links. You could also create a global variable or some such named {secure_url}, and then create links to {secure_url}/path. In some cases, it might be easier to let the browser link through to the HTTP page, and then let your enforcement rules redirect the user to HTTPS.

You also need to make sure that any pages submitted inside your secure sections are submitted to HTTPS URLs (otherwise it defeats the point of having secure pages!). SafeCracker has a handy secure_action parameter to handle this. Our Store module also has a secure=“yes” parameter for most tags which enforces HTTPS for viewing the current page, and sets a secure action URL. Unfortunately, the standard EE login tag doesn’t have anything to handle secure actions (we will be adding this to FreeMember very soon).

There are modules out there which help you with generating secure page links in ExpressionEngine. The problem currently is that they affect ALL links on your site. This means that once a visitor reaches the secure section of your site, all links to the rest of your content will be HTTPS. Therefore, we prefer to just manually specify which links are HTTPS, and leave the standard EE site_url and path=”” variables unchanged.

To enforce HTTPS on your specific site sections, you can use a variation of the .htaccess file above:

RewriteEngine On
RewriteCond 
%{HTTPS} off
RewriteCond 
$^(member|account|checkout|system[NC]
RewriteRule 
(.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] 

This tells your server “If HTTPS is off, and the request starts with member OR account OR checkout OR system (not case sensitive), redirect to https://current-domain/current-page”. It’s a nice simple method of locking down entire subfolders / template groups.

Removing links to insecure content

One final thing you will notice once you have secure pages up and running - some browsers (mainly IE8+ and Chrome) will complain if elements of the page are loaded insurely. This is a good security feature - there’s no point loading the main HTML securely if your CSS is then loaded over an insecure connection. A malicious user could simply steal the cookies being sent along with your CSS request, and use them to access the secure section of your site.

Therefore, you need to make sure all page resources are loaded securely too. In Chrome, if you open the developer console, you will find a list of any resources which were loaded insecurely. Note - Chrome has some pretty extreme caching, so will often keep complaining even after you have fixed an issue (and likewise, often keeps using old cached redirects). The best option is to open a new Private Browsing window every time you make a change, to test it from a clean state.

The most common things you will need to fix are CSS and JS files, and external images. There isn’t an easy way to use EE’s nice CSS syntax over HTTPS, so we usually just use a relative reference:

Before: <link rel="stylesheet" type="text/css" href="{stylesheet=styles/index}" />
After:  <link rel="stylesheet" type="text/css" href="/styles/index" /> 

You might also want to add a cache-buster, such as ?v=20110702 to your css files, and change the number each time you publish changes. When you start referencing CSS files like this, it’s important to remember image references in CSS files are relative to the directory of your CSS file. Therefore, you will probably need to go through and make sure all image references are relative to your site root too:

Beforebody { backgroundurl('images/layout/background.jpg'); }
After
:  body { backgroundurl('/images/layout/background.jpg'); 

You also need to fix up references to JS files so that they are relative to your website root in a similar manner.

Finally, you will probably notice most CDN content (such as google fonts, or javascript files) you have is also delivered over HTTP. For example, we have the following CSS:

<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=PT+Sans" /> 

Luckily, Google allow access to their CDN over both http and https. The lazy way to fix this would just be to hard-code HTTPS, but that’s not very good for performance/caching is it! The best way to solve the problem is with some clever PHP in your template:

<?php echo empty($_SERVER['HTTPS']) ? 'http' 'https'?>://fonts.googleapis.com/css?family=PT+Sans 

This will work regardless of whether you have PHP set to “input” or “output”. We also have a simple module in the pipeline for outputting the current protocol, enforcing https on certain templates, and a few other handy tricks, so if you like to avoid messy PHP in your templates, watch this space!

Getting HTTPS working well on your site can often seem like a long process, but it is certainly worth it! If you are allowing users to submit sensitive data (such as credit cards) directly to your website, it is essential that this is done over HTTPS. However, even with (slightly) less critical data, such as usernames/passwords, it is good security practice.

How do you handle EE-over-HTTPS? Do you have any tips to add to this list?