Rails, Apache, Passenger, Secure Cookies, and SSL
Since I haven’t written to this blog in ages, and I have been spending a lot of time working on Rails applications, and I need to document some of the processes of my work somewhere, I figured I’d put them here for the edification of anyone who finds him or herself needed the same questions answered that I did. I definitely googled a shit-ton while looking for answers, and as I did that I thought to myself “My lord, people really do spend a lot of time helping others for no material gain. How have I contributed?” I hadn’t, until now. For all you loyal readers of my writing who expect the regular diatribes concerning the regular things people rant about (politics, art, etc), you can safely ignore these posts without missing too much of me. So here goes:
One of the sites I’ve been working on was found to have a security hole (I won’t say which site, for obvious reasons). It turns out that with Rails, if you sniff or otherwise procure the session ID that’s set in a visitor’s cookie (which is normally sent as plain text), you can then make requests to that Rails app as if you were that visitor as long as the session ID is still valid (meaning you could pretend to be that visitor without actually knowing his/her username or password — all you need is that session ID).
One of the solutions to this (besides making the entire site secure and served via https) is to set a secure authentication token (in the form of a cookie) when a visitor logs in. So now we have two cookies: the session cookie that contains the plain-text session ID, and the authentication token cookie, which, for now, contains an encoded string that I’ll explain below. When a visitor logs in to the secure login page, the authentication token is set and sent over https. Then, for the rest of that visitor’s browsing session (meaning until he/she closes his/her browser), all secure requests from that visitor ask if the authentication token is present and valid. If it is, then the request works fine and the visitor gets back whatever page he/she was requesting. If not, the request is redirected to the login page. Since the authentication token is always sent via https and never as plain-text, the only way an attacker could steal it is to harvest the cookie straight from the legit visitor’s machine (via some sort of spyware or virus script).
So I had to implement all this, but because my development environment used Mongrel, I had no way to serve https pages locally (Mongrel, which is the defacto Rails server, has no built in https support). So I had to change the way I was doing things on my local box before I could code and test my secure authentication cookie. I did some research, and found that the best and easiest solution was to install Passenger (aka mod_rails) as an Apache module, and then use Apache to serve all my pages locally (Apache has SSL support). While I’d deployed onto boxes with Passenger, I’d never considered installing it locally (I guess because it’s not available on Windows and I’ve only just started using a Mac again fairly recently). It turns out it’s a cinch to install, and on OS X there is also a Preferences pane that you can install to make setting up local sites that much easier.
So once you have Passenger set up properly and the (Apache) virutal hosts set up on your development machine, you should create a self-signed security certificate, which you can find instructions to do here. This will be used for your secure pages, and should only be used in development or staging environments because your browser will inevitably throw up a warning when you hit a secure page using this certificate. In production, you have to get some authority to sign your certificate (like GoDaddy.com or some other secure certificate service).
Once I had all that set up, then it was time to modify my Rails app to allow it to serve secure pages, and to set up the login and admin pages to always serve via https. This is easily done once you install the ssl_requirements plugin into your Rails app. It’s a small plugin that lets you easily require or allow certain pages of your Rails app to be served over https — just add something like
class AccountController < ApplicationController ssl_required :signup, :payment ssl_allowed :index
to your controller. This will force the signup and payment actions of that controller to use https (any requests to those pages via http will be redirected), and this will allow the index action to be served via https, but also regular http. All other actions of this controller will be forced to use http, and will be redirected to use that protocol if the inital request is https.
That’s pretty much it. Now I can test https locally, and I can easily run multiple rails apps simultaneously. If you read this and want to know any specifics, feel free to leave a comment.








