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

Posted July 28, 2009 at 9:24 AM

Tags: ColdFusion, Ask Ben

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

 Launch code in new window » Download code as text file »

  • <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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page



Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Jul 28, 2009 at 10:14 AM // reply »
9 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.


Jul 28, 2009 at 10:24 AM // reply »
6,516 Comments

@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.


Jul 28, 2009 at 10:31 AM // reply »
14 Comments

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.


Jul 28, 2009 at 10:31 AM // reply »
6 Comments

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.


Jul 28, 2009 at 10:35 AM // reply »
6,516 Comments

@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).


Jul 28, 2009 at 11:20 AM // reply »
9 Comments

@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.


Jul 28, 2009 at 11:45 AM // reply »
23 Comments

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>


Jul 28, 2009 at 2:13 PM // reply »
6 Comments

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.


Jul 28, 2009 at 2:18 PM // reply »
23 Comments

In my case, we're running CF on IIS, so changes at the Apache level is not an option.


Jul 28, 2009 at 3:31 PM // reply »
55 Comments

yes, content behind ssl can/will be indexed so long as it's publicly available (i.e., you don't have to be "logged in" to view it).


JC
Jul 28, 2009 at 4:49 PM // reply »
7 Comments

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.


Jul 28, 2009 at 5:16 PM // reply »
27 Comments

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.


Jul 28, 2009 at 6:38 PM // reply »
6,516 Comments

@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?


JC
Jul 29, 2009 at 8:49 AM // reply »
7 Comments

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.


Jul 29, 2009 at 4:44 PM // reply »
78 Comments

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


Aug 4, 2009 at 3:21 PM // reply »
6 Comments

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.


Aug 4, 2009 at 3:50 PM // reply »
27 Comments

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.


Aug 4, 2009 at 5:05 PM // reply »
78 Comments

@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.


Aug 4, 2009 at 7:39 PM // reply »
6 Comments

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).


Aug 4, 2009 at 8:23 PM // reply »
78 Comments

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.


Aug 5, 2009 at 8:31 AM // reply »
6,516 Comments

@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.


Aug 5, 2009 at 8:54 AM // reply »
78 Comments

@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).


Aug 5, 2009 at 9:01 AM // reply »
6,516 Comments

@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).


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 20, 2009 at 11:32 PM
Five Months Without Hungarian Notation And I'm Loving It
I've used headless camel case for years for not only ColdFusion variables, but also SQL tables and fields... pretty much everything involving code. I also subscribe to the "don't abbreviate and clea ... read »
Nov 20, 2009 at 11:00 PM
Five Months Without Hungarian Notation And I'm Loving It
@Marcel, Yeah, I always err on the side of longer but more readable variable names. As for the camel casing of CF methods and the headless camel casing of custom items, I get around this by always ... read »
Nov 20, 2009 at 10:56 PM
Five Months Without Hungarian Notation And I'm Loving It
I use the following and love it: my.namespace.MyComponents.functionMethodsOrUDF() CONSTANT_VALUES_OR_PROPERTIES One thing I always try is to CamelCaseBuiltInColdFusionFunctions() so others can tell ... read »
Nov 20, 2009 at 5:38 PM
Learning ColdFusion 8: CFImage Part I - Reading And Writing Images
Hi Ben, Great article. I've been looking around to see if ColdFusion image engine can programatically create the following "wrap around" effect: http://www.creativepro.com/article/photoshop-s-she ... read »
Nov 20, 2009 at 5:35 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Dave: I talked to Gert he suggested: <cfhttp method="get" url="http://{some cf website}" result="stuff" addtoken="yes" /> Note the addition of cfhttp attribute addtoken. That should persist y ... read »
Nov 20, 2009 at 5:23 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Todd, Ahh, gotcha, yeah that makes sense. ... read »
Nov 20, 2009 at 5:17 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
Ben, sorry if I didn't make this clear. You can make it work like that if you want, just put <cfset session.foo = 1> (and <cfset application.foo = 1>) in your OnRequestStart() and it reve ... read »