Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with:

Ask Ben: Enforcing An SSL (HTTPS) Connection Based On Request

By Ben Nadel on

Hey Ben!! I have a question for you that I really need help with. I have developed a website that didn't need to be encrypted. Now I was told that I had to incorporate SSL encryption. The problem is that user have the website http://yoursite.com bookmarked, and now when they come to the website they get a loading error on the website. What I need is some how to find out in the application.cfc what URL you are coming to i.e. http://yoursite.com or https://yoursite.com If you are coming from http://yoursite.com redirect you to https://yoursite.com I need to do all this before the application loads. Any Idea how to do this? I just not sure on how to pull in the protocol into the function. Thanks in advance.

Switching from SSL to non-SSL connections and vice-versa is something that's been blogged about a bunch of times, but I've never blogged about it personally. And so, I'll take this opportunity to share my methodology. To accomplish this request-switch, all we need to do is figure out two things:

  1. Is the current request using an HTTPS connection?
  2. Does the current request require an HTTPS connection?

Fortunately, the first piece of information is very easy to figure out. Using the CGI object, we can check the use of https using: cgi.https. This value of this property is not quite a boolean, but rather a on/off value. If the current request is using HTTPS, the value of cgi.https will be "on"; and, if the current request is not using HTTPS, the value of cgi.https will be "off."

The second piece of information - whether or not the current request requires an HTTPS connection - is more complex to figure out because the rules dictating this vary from application to application. Sometimes this is done based on the requested file; sometimes this is done based on the parent directory; sometimes this is done based on an action variable (ex. URL.action, URL.fuse); and, sometimes this is done based on something else altogether. That said, the solution I am providing shows a few options, but is in no way exhaustive.

Because this logic can be complicated, I have factored it out into its own function. This way, our logic is centralized and easy to read and maintain. And, because this logic needs to be run for every page request, I have put it right in the Application.cfc:

Application.cfc

  • <cfcomponent
  • output="false"
  • hint="I define the application settings and event handlers.">
  •  
  • <!--- Define the application. --->
  • <cfset this.name = hash( getCurrentTemplatePath() ) />
  • <cfset this.applicationTimeout = createTimeSpan( 0, 0, 0, 30 ) />
  •  
  •  
  • <cffunction
  • name="onRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the request.">
  •  
  • <!---
  • Check to see if we need to enforce an SSL connection
  • (or enforce a non-SSL connection). We are going to
  • break this out into another method so that we can
  • reuse it without duplicating the logic. Also, this
  • will allow us to change the enforcement rules in a
  • central place.
  • --->
  • <cfset this.enforceSSLRequirement() />
  •  
  • <!--- Return true so the page can load. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="enforceSSLRequirement"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I check to see if the current request aligns properly with the current SSL connection.">
  •  
  • <!--- Definet the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Get the current directory in case we need to use
  • this for SSL logic.
  • --->
  • <cfset local.directory = listLast(
  • getDirectoryFromPath( cgi.script_name ),
  • "\/"
  • ) />
  •  
  • <!---
  • Get the current file in case we need to use this
  • for SSL logic.
  • --->
  • <cfset local.template = listLast(
  • cgi.script_name,
  • "\/"
  • ) />
  •  
  • <!---
  • Check to see if the current request is currently
  • using an SSL connection.
  • --->
  • <cfset local.usingSSL = (cgi.https eq "on") />
  •  
  • <!---
  • By default, we are going to assume that the current
  • request does NOT require an SSL connection (as this
  • is usually in the minority of cases).
  • --->
  • <cfset local.requiresSSL = false />
  •  
  • <!---
  • Now, let's figure out if the current request requires
  • an SSL connection. To do this, we can use any kind of
  • url, form, directory, of file-based logic.
  • --->
  • <cfif (
  • (local.directory eq "account") ||
  • (local.directory eq "checkout") ||
  • (
  • structKeyExists( url, "action" ) &&
  • (url.action eq "top-secret")
  • ))>
  •  
  • <!---
  • This request does require an SSL. Set the flag
  • for use in further logic.
  • --->
  • <cfset local.requiresSSL = true />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • At this point, we know if we are currently using
  • SSL or not and we know if the current request
  • requires SSL. Let's use this information to see if
  • we need to perform any redirect.
  • --->
  • <cfif (local.usingSSL eq local.requiresSSL)>
  •  
  • <!---
  • The current page is synched with the SSL
  • requirements of the request (either is requires
  • and IS using an SSL connection or it does not
  • require and is NOT using an SSL connection).
  • Just return out of the function - no further
  • action is required.
  • --->
  • <cfreturn />
  •  
  • <cfelseif local.requiresSSL>
  •  
  • <!---
  • At this point, we know that we need to do some
  • sort of redirect, and because the requrest
  • requires an SSL connection, we know we need to
  • redirect to an HTTPS connection. Let's store the
  • protocol for use in the following CFLocation.
  • --->
  • <cfset local.protocol = "https://" />
  •  
  • <cfelse>
  •  
  • <!---
  • At this point, we know that we need to do some
  • sort of redirect, and because the request does
  • NOT requiere an SSL connection, we know we need
  • to redirect to an HTTP connection. Let's store the
  • protocol for use in the following CFLocation.
  • --->
  • <cfset local.protocol = "http://" />
  •  
  • </cfif>
  •  
  • <!---
  • If we've made it this far, then we are redirecting
  • the user to a different page based on the chosen
  • protocol. Build the target URL.
  • --->
  • <cfset local.url = (
  • local.protocol &
  • cgi.server_name &
  • cgi.script_name &
  • "?" &
  • cgi.query_string
  • ) />
  •  
  • <!--- Redirect the use to the target connection. --->
  • <cflocation
  • url="#local.url#"
  • addtoken="false"
  • />
  •  
  • <!--- Return out (NOTE: we will never make it here). --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

SSL connections are slow. As such, we only want the user to be using them when they have to. Therefore, it is just as important to enforce an HTTP connection as it is to enforce an HTTPS connection. In our Application.cfc function above, enforceSSLRequirement(), notice that I am forcing the current request to align itself properly with the requirements of the page, even it the current page does not require an SSL connection. In this way, we don't have to worry about what happens when a user leaves a secure part of the site to explore non-secure parts.

Keep in mind that the CFLocation we are using to forward the user to the proper protocol will break any form posts. This kind of code is not meant to auto-wire all of your HTTPS connections - you still need to post to the appropriate protocol from within your HTML forms (if necessary). This code is only meant to enforce SSL requirements - not to perform all SSL logic.

Like I said above, determining which pages require an SSL connection will require different logic from application to application. This demonstration was not meant to be exhaustive, but rather to examine how these SSL checks can be performed and enforced for each page request. I hope this helps.



Reader Comments

Hi Ben,

Food for thought:

<cfset local.url = local.protocol & application.domainName>
<cfif cgi.script_name neq '/index.cfm'>
<cfset local.url = local.url & cgi.script_name>
</cfif>
<cfif cgi.query_string is not ''>
<cfset local.url = local.url & '?' & cgi.query_string>
</cfif>
<cflocation url="#local.url#" addtoken="no" statuscode="301">

This makes sure the user is at the domain name we want (maybe we want to force 'www.').
Then it only uses query strings if needed. It's a cleaner url.
And it uses 301 status code so search engines get the hint - stop indexing the page with the protocol we do not want to serve through.

@Jules,

Yes, enforcing a given sub-domain is also a really good idea. As far as the 301 is concerned, I think that makes more sense for the sub-domain idea and less sense for the SSL connection. Ideally, I don't think you want a search engine to be indexing things that are behind an SSL connection. I wonder if they even will? Hmmm, never thought about that.

I'd also note that the cgi.https flag doesn't seem to respond well when behind a hardware load balancer. At my last job, the connection was secure to the load balancer, but not between the load balancer and the CF servers, so CF never saw itself as being on HTTPS. I'm not sure if that was just an issue with the hardware configuration though, so it may be different other places.

Ben, you have to be careful using the CGI object. In our setup, SSL is done outside of the web server, on either accelerators or our load balancing server so the value of CGI.http is alwasy 'off'. To get around this, we constructed custom headers only set by the web server, as it receives http requests on one port and https requests on the other, as determined by the load balancer.

@Jon, @Phil,

Very interesting. I have not had the opportunity yet to play with a load balancer. Sounds like each solution is a bit different (between you and Jon).

@Ben,

My mind always thinks 'ecommerce' when I hear SSL. But of course SSL is ideal for extranets, too.

Following the thought of ecommerce... the largest way a visitor comes to the site is through a search engine. If you want to force visitors to use a certain protocol, then the best way to achieve that is to have them come over with that protocol in the first place.

And if the site is private, the 301 doesn't hurt anything anyway.

Here's a solution I found a few months back that I'm using on a CF7 site (that started as CF6). The site is still using Application.cfm instead of Application.cfc. This is the first few lines of code in Application.cfm.

<cfif not CGI.SERVER_PORT_SECURE>
<cfif CGI.QUERY_STRING neq ''>
<cflocation url="https://#CGI.SERVER_NAME##CGI.SCRIPT_NAME#?#CGI.QUERY_STRING#" addtoken="no" />
<cfelse>
<cflocation url="https://#CGI.SERVER_NAME##CGI.SCRIPT_NAME#" addtoken="no" />
</cfif>
</cfif>

I'd rather handle the protocol switch at the Apache level if the application needs to be completely protected by SSL at all times.

<Directory "/path/to/your/app">
# Force SSL on
RewriteEngine on
RewriteCond %{HTTPS} !=on
#RewriteCond %{SERVER_PROTOCOL} !^http [NC]
RewriteRule ^(.*) https://%{HTTP_HOST}/$1 [NC,L,R=301]
</Directory>

See http://claude.betancourt.us/how-to-force-a-url-to-use-https/ for more details.

I usually handle this with IIS.

1) Create an empty index.htm page
2) Set index.htm as a server-side redirect to https://yourdomain.com/yourdefaultpage.cfm
3) Change the "SSL Required" error page to index.htm
4) Force SSL sitewide
5) Exempt index.htm from SSL requirement.

I assume you could easily do the same thing with apache in htaccess.

For some reason I went the route of creating a custom error template in IIS for this failure, which redirects, by way of a CF page, to the https address. I seem to recall that if I used SSL I had to require it, but it has been a while since I set this up so I could be mistaken. Otherwise it would seem that per-application redirection would be the way to go.

@Claude,

Does doing a rewrite on the HTTP->HTTPS actually do the same thing as the client actually making an HTTPS connection? Isn't there some kind of encryption that needs to be taking place on both ends for the HTTPS to work? It would seem that if this was being on via a rewrite, it would only be "on" for the server, but not for the client?

@JC,

That's an interesting idea. How do you force SSL such that an SSL error might be thrown. Is that a setting in IIS itself?

Aye, Ben.

In IIS, right click on a web site and choose properties > Directory Security > Secure Communications > Edit > Require Secure Channel (SSL)

Then do the same thing on the default file and uncheck it there, which exempts it.

I think I left out one step though -- you have to change the default homepage for the site to the nonsecure redirect one as well. That way people going to the / of the site get handled properly as well.

Apache's mod_rewrite is very odd. Claude's example will actually cause Apache to issue an HTTP redirect (what <cflocation /> does).

I was searching this topic thinking that the basic way to do this was on the secure pages use cflocation if the url is not already https. All other pages would be forced to be non SSL. I am surprised to see so many more complicated solutions. Usually only a few pages need to be secure, so I thought that would be the way to go. You all have me thinking that I am oversimplifying this.

Mark, When you say some pages do you mean only encrypting the login for example, or maybe only pages with sensitive info? I'm no expert on the subject but here are my thoughts. Many people don't initially think about the fact that every request to a domain/app includes applicable cookies. One thing to consider is that if someone has your cookies they can access the application as if they were you as session is based around cookies. They don't need your username and password. Requiring encryption for an entire application will not only encrypt the data submitted and pages accessed but also keep cookies secure from this sort of session hijacking.

@Mark, to a person who is an Apache/IIS expert and ColdFusion hacker, Apache/IIS configuration is dead simple while writing ColdFusion code to enforce something as simple as an HTTPS requirement is far too complex for the requirement.

I was thinking ecommerce, so login, and checkout pages. The more I think about it, the more I think that I need a url variable to distinguish http or https as Ben has done (top-secret).

I can't imagine any situation where a url-param indicating secure-connection-is-required would be useful. What I can imagine, however, is how insecure such a url-param would be.

@JC,

Ah, ok interesting. I've never seen that before. Cool tip.

@Justice,

Are you saying that the mod-rewrite shouldn't be doing a relocation? Or are you just clarifying that that is what it is doing (not just altering the script from the server standpoint)?

As far as a URL parameter determining security, this would happen in any front-controller based application where there are no "pretty" urls. For example, you might have a bunch of urls that look like:

index.cfm?action=account
index.cfm?action=help
index.cfm?action=security.login

In this case, the "action" url parameter would be the only thing that determines SSL requirements.

@Ben, mod_rewrite has a particular way of working which is not necessarily intuitive to everyone immediately. I was simply noting that the given example caused a HTTP 301 redirect, as can be seen from the "R=301" attribute.

A url-param like "?require-secure=true" is not very secure; that type of url-param seems to be what was implied. Using url-params to mimic Rails path routing or to mimic Django path matching, and then determining whether security is required for a given action, where the action requested is determined by the url-params, is perfectly reasonable (when done in a good way).

@Justice,

Ah, ok, that's what I figured you were saying. I know nothing about mod-rewrite, so nothing is obvious to me. I did not realize that R=301 did a redirect, thanks for the tip.

As far as URL-parameters, I agree with what I think you are saying - a parameter that determines security alone is not good; but, if the URL parameter is used for page flow, then it's all good (as the security enforcement is coupled to the workflow, not left on its own).

Is there any security concern if you first hit a cfm as http and it creates CFID, CFTOKEN and JSESSIONID values and then you redirect to https?

I'm wondering if those values could possible be captured by a packet sniffer and, then the evil-doer could be used to spoof the person while creating an ssl connection separate from the original person.

In short, should the ID information in the session be rebuild under ssl for security purposes?

Tom

@Tom Hubbard just stumbled across this post (3 years too late) but wondering if there was any more recent feedback on this issue.

I too have an application that performs a 302 redirect on non HTTPS detection and it appears a non-secure JSESSIONID is created when the HTTP site first hits the Application.cfc and another new secure JSESSIONID is created after the redirect that occurs in the onRequestEnd().

I'm curious as to whether that initial non-secure cookie poses any security risk for the application and if so how can it be prevented?

Phil