Coding is like gardening...

Archive for March, 2009

Under the hood: Not-so-basic authentication

Recently I worked on a project that required a single login to access administration options. There was no need for a full-blown RESTful authentication solution – I was advised to “Just use basic auth!”

Rails makes it easy. You probably know the standard example. You put a before_filter :authenticate in the controllers that require it, and set it up in the Application Controller.

def authenticate
  authenticate_or_request_with_http_basic do |user, password|
    user == 'admin' && password == 'pass'
  end
end

It’s all well and good … until you want to add a log out button. The browser stores the successful login credentials in a sort of cookie, and applies them to every page which requests basic authentication. Once you’ve logged in, it’s actually quite hard to make the browser forget you until you quit and restart the browser. It’s hard, but not impossible. If you can force basic authentication to fail, the browser will throw away the credentials.

So the solution is to add a session variable that says “NO SRSLY, LOG ME OUT PLS!” This is the logout action (a destroy method in a Sessions Controller)

def destroy
  session[:logout_requested] = true
  flash[:notice] = "You have logged out successfully"
  redirect_to(root_path)
end

Now for the tricky bit. The way this works is subtle and takes a moment to figure out each time I think about it. We change the authenticate method in the Application Controller so that as well as checking the username and password, it also ensures that this flag has not been set. Meaning we can cause basic authentication to fail when we want it to.

def authenticate
  authenticate_or_request_with_http_basic do |user, password|
    user == 'admin' && password == 'pass' && session[:logout_requested] != true
  end
  session[:logout_requested] = nil
end

Next time our user goes to a page which requires authentication, the browser still provides the correct username and password, but the flag causes the basic authentication to fail. Obviously we then have to clear the flag straight away, otherwise the user would not be able to get back in again even with the correct credentials. The user must type in the correct login name and password again to be able to get back in.

Perhaps we want to know whether the user is logged in or not, so that we know whether to display an edit button. We can set another session variable. Conveniently, authenticate_or_request_with_http_basic returns a boolean value.

def authenticate
  session[:logged_in] = authenticate_or_request_with_http_basic do |user, password|
    user == 'admin' && password == 'pass' && session[:logout_requested] != true
  end
  session[:logout_requested] = nil
end

Remember to set the flag to false when you log out. Also remember that this flag could be true, false or nil so a check in the Application Controller looks like this:

def logged_in?
  session[:logged_in] == true
end

Finally it’s worth noting that the username and password do not have to be hard-coded like this. It’s simple for an example, but don’t think that’s all there is to basic authentication. There’s nothing to stop you comparing against values in a settings table or even doing a user lookup à la RESTful authentication.

def authenticate
  session[:logged_in] = authenticate_or_request_with_http_basic do |email, password|
    user = User.authenticate(email, password)
    if user && session[:logout_requested] != true
      self.current_user = user
      true
    else
      self.current_user = nil
      false
    end
  end
  session[:logout_requested] = nil
end

Thanks to Richard and Tris for their help in figuring out the not-so-basic aspects of basic authentication! :)

Want to work for us?

A quick note to draw your attention to the fact that we’re hiring again… this time we’re looking for mid-level or senior web developers. Do have a look at our jobs pages for more information.

Hiring in the midst of a recession is always a bit scary, but we’re finding that we’re very well placed as a company to withstand it. I’ve never been busier writing features and following up leads, and we’re rapidly becoming well-known for our team’s excellent work (I’m so proud of them :-)

So, if you’d like to join us, now is the time.

Pivotal Tracker: Fantastic app, shame it’s free

I’ve been digging around for a decent agile PM tool for a while, and stumbled across Pivotal Tracker on Twitter a couple of days ago.

Within about thirty minutes of trying it out, I was totally hooked. We’ve rolled our own basic project management tool internally, but we’ve yet to get the time to put proper iterative development support into the app. Pivotal Tracker just works. With a clean UI and some great reporting tools, it’s all I need to run an agile project.

The one big downside is this: it’s completely free.

Wait, you say: surely that’s a good thing? Well, yes and no.

Why great software should cost money

When I pay for an app on the net, I expect a few things:

  • It’s going to be up pretty much all the time.
  • It’s not going to vanish tomorrow.
  • It’s not going to suddenly cost a fortune.

With Pivotal Tracker, I’ve no idea when they’re going to take the software down for maintenance. I’ve seen reports of it going down without notices for 15 minutes here and there, which is fine if it’s a non-critical service. It is however most definitely not fine when my client is trying to comment on features. I also have no guarantee that it’s not going to suddenly disappear into the night, taking all my stories and possibly the project itself with it.

The other issue is that if I’m paying $20/month for it, I’ve a reasonable expectation that it’s not going to cost say $200/month next month. It’s well within Pivotal’s rights to withdraw the free service and charge what they like for the paying version, without any notice.

Are we going to use it anyway? Yes we are: it’s too good to ignore. However, we’ve put in place the following cron job to mitigate the risk:

*/20 * * * * curl -H "X-TrackerToken: " -H
 "Content-type: application/xml" -X GET
 https://www.pivotaltracker.com/services/v2/projects/39382/stories
 > stories-backup.xml

This little piece of magic copies all our stories in XML form to our backup server every 20 minutes, so we’re not screwed if the site vanishes without notice. It would only take us about an hour to import this XML into our old system should we need to. That takes care of the main risk for me.

The conclusions: Some software shouldn’t be free. If you’re using Pivotal Tracker or any other free service in anger, make sure you’ve got a backup plan.