Get rid of user passwords in Rails using SSL client certificate
For an application I'm writing, I needed a way to log in without having to enter a password. You can authenticate a client against a server in Apache using SSL client certificate validation. The problem was to create a link between Apache and the Rails application running under Mongrel. Here is what I've done.
Apache and SSL
First, you'll need Apache set up with SSL and client certificate validation. There are howtos on the web on how to to that, so I won't explain that in full details here. You'll need a server private key and certificate and a client certificate issued by the same authority (or authorities trusting each others). For myself, I use CAcert which provides free "ethic" certificates.
I use Coda Hale great article to configure my rails apps. Here is what I added to the Apache configuration for having it working with SSL and client certificate validation. Please note that only the lines added or changed are listed here. For it to work, you need to enable the Apache 2 SSL and headers modules.
SSLEngine on
SSLCertificateFile /path/to/cert.pem
SSLCertificateKeyFile /path/to/private.key
SSLCACertificateFile /path/to/cacert.pem
SSLVerifyClient none
SSLVerifyClient require
SSLVerifyDepth 1
RequestHeader set X_FORWARDED_PROTO 'https'
RequestHeader set X_SSL_CLIENT_DN_Email "%{SSL_CLIENT_S_DN_Email}s"
So what's the meaning of all this ?
On line 1, we say that this virtual host is for connection on port 443 (don't forget to add a "Listen 443" directive to your Apache config).
Line 2 to 5, we simply configure server side ssl.
On line 6 to 12, we configure client certificate validation. The line 6 is usefull if you want to require validation only for some specific locations (say 'admin' for example). Line 9, we require client certificate validation for the '/' location. Line 10 says that we need to go up in the authority hierarchy of 1 level maximum (my client and server certificates are issued by the same authority).
On line 13, we tell rails that we are using the "https" protocol so that links outputted by call to "link_to", "url_for" and the likes need to start with "https" instead of "http".
SSL, Rails and Mongrel
This is the interesting part, specific to using Rails with Mongrel.
On line 14, we say to Apache to add the "X_SSL_CLIENT_DN_Email" header to the value of the "DN_Email" field in the SSL client certificate. This is the only way I have found to forward to Mongrel the SSL client certifcate values that are of interest to us.
Now in your controller, to fetch the email of the user connected:
1 2 3 4 5 6 7 |
class ApplicationController < ActionController::Base before_filter :ssl_login def ssl_login session[:user] = User.find_by_email(request.env["HTTP_X_SSL_CLIENT_DN_EMAIL"]) end end end |
If your Mongrel instances are only listening to your local host network interface (think 127.0.0.1), this should be secure:
- Apache will prevent anybody not having a valid client certificate to even reach your Mongrel instances,
- for the others, Apache sets the email of the certificate in a header and overwrite the header if already present (so client are unable to forge that header),
- assuming a common authentication scheme, if the user email is in your database, he is allowed to connect.
Please do note that if a hacker is able to connect to your Mongrel instances via your local host network interface, he can forge the header and pretend to be someone else. But if he has access to your local host interface, you probably are already in big troubles.
I hope you found this useful, any comments are of course welcome.
Comments
-
Very Nice implementation, but how do you setup your browser to ease the adoption ?
-
On cacert.org, they install it automatically and it seems it's working simply with a link to download the certificate.