If you’re starting out with SSL and Rails, you’ll probably stumble upon ssl_requirement very quickly. It promises to make routing easy by automatically redirecting to SSL when required (hence the name…). However, in most cases, that’s not enough, and relying solely on ssl_requirement will leave you unprotected.
ssl_requirement really only protects you in one direction, when the client requests data that should be encrypted when sent from the server. However, it does not really do much for you in the oh-so-important case of transmitting sensitive data from the client to the server.
Now, if your entire site is SSL (ie. Apache redirects all incoming requests to HTTPS), then it’s not really a problem. Your form_tag or form_for calls will pick up on the fact that they are being served from an SSL protected page, and they themselves will submit to HTTPS. However, in the case of a non-encrypted page that has a form that should be encrypted (ie. login form on the homepage), the form will default to submitting to regular HTTP, since it defaults to use the protocol of the current page. This is where ssl_requirement does nothing to help us.
In this case, the client will POST the form unencrypted to your ssl_requirement protected action. ssl_requirement will determine that this particular action requires SSL, and sends a redirect to the HTTPS action, which the browser happily complies with. Unfortunately, at that point, it’s already too late, since the first transmission was unencrypted. Nothing breaks, and everything looks fine, but each and every form submission is being sent twice: once in the clear, and once with encryption. Not really what we wanted, right?
One solution is to always use named routes and set the protocol in the routing file. In this case, you must always use xxx_url (not xxx_path) in your form_for and form_tag calls. I have not personally verified that this works, but it seems like a decent solution.
Another way is to hack together alternate form_for and form_tag methods. These new helpers will test whether you’re currently in production or development mode and generate the HTTP or HTTPS form submission URLs accordingly. This is what we did for RioFlexPay, and it works fairly well.
In the end, we got rid of ssl_requirement altogether. It simply provided very little for us, and started to conflict with our Apache settings. In our case, we wanted the homepage to be unencrypted, but wanted all other pages to use SSL. This was fairly easy to set up with Apache rewrite rules. Unfortunately, this caused conflicts with ssl_requirement. The ssl_requirement plugin would see an action that wasn’t explicitly listed as allowing SSL and would redirect it to HTTP. Meanwhile, Apache would see an HTTP request for a non-homepage URI and redirect it to HTTPS. Thus, many of our actions resulted in infinite redirect loops, and of course we didn’t see this until we deployed to production, where SSL is enabled. Believe me, that was a late night of furious debugging. Simply removing ssl_requirement and allowing Apache to handle everything was our final solution.
So, just remember: ssl_requirement is not a magic bullet for SSL. You really have to step back and examine what you do and don’t want encrypted, and you need to think in terms of both client request and server response. Once you’ve decided on that, it’s time to make sure that your Apache rewrite rules, your ssl_requirement settings, and your link_to, form_for, and form_tag calls are all set up correctly. Only then can you rest easy.
November 4th, 2008 at 10:32 am
I just started out SSL on Rails. I found this fork of ssl_requirement on github: http://github.com/bcurren/ssl_requirement/tree/master
I think it might address most of your concerns. Specifically, you can declare what ISN’T ssl-enabled, and you can also easily make sure your forms are ssl protected.
November 4th, 2008 at 10:35 am
Thanks for the tip, Josh!
July 25th, 2009 at 7:50 am
[...] http://blog.aisleten.com/2008/06/02/beware-of-ssl_requirement/ I’m not certain of the validity of this or whether its been addressed. [...]
September 20th, 2009 at 7:19 pm
Whatever happened to DRY?
That you have to jump through so many hoops to get this to work is frankly shockingly poor. The idea of http://github.com/iwarshak/secure_actions seems to be on the right track, but that plugin has a big “DO NOT USE THIS PLUGIN” at the top.
I’ve just installed Redmine, and couldn’t believe that having made it so easy to connect to an LDAP server to authenticate, it is so hard to get it to secure the login form.
Crazy. Only solution for now is to require SSL for the whole shooting match.
January 8th, 2010 at 7:16 am
it’s also important to consider that pages fetched over an insecure protocol (http) that contain forms that are intended to submit sensitive data over a secure protocol (https) are also vulnerable to Man in the Middle (MiM) attacks.
This is due to a lack of authenticity. In addition to encryption, SSL also provides the client with a way to ensure that the sender of the data is who they say they are. That’s why they make you buy a certificate from a certificate authority like Geortrust or verisign. Without it, a man in the middle could alter the data sent back from the server on the insecure request. For example they could change the form so that the action attribute points to their server instead of yours.