trek (owner)

Revisions

gist: 61811 Download_button fork
public
Public Clone URL: git://gist.github.com/61811.git
Embed All Files: show embed
auth.mdown #

Authentication Ain't My Domain

I've never worked on a web application before where there wasn't some authentication feature expected. There's always some portion of the application we want restricted to certain users or logic that requires a form of continued personal data ownership. Despite this need, putting authentication into an application is a frustrating task compared to the rest of Rails.

Rails developer have had a few options, each with their own advantages and drawbacks:

  • Write it yourself (the only option in the early days). Advantage: only the code you need. Drawback: you'll write lots of similar looking code in each projects. Testing and maintaining sucks.
  • Use LoginGenerator (dead and gone now). Advantage: it was someone else's code. Drawback: it used a generator and put someone else code into your models. Was not even close to being Restful, nor was it really idiomatic Rails.
  • Use acts_as_authenticated (depricated). Advantage: it was someone else's code, well written and tested. Drawback: it used a generator and put someone else code into your models. Wasn't RESTful.
  • Use restful_authentication. Advantage: well written, well tested, RESTful. Drawback: pretty much the same as acts_as_authenticated, which it replaced.
  • Use authlogic (my current recommendation). Advantage: someone else's code, well written and tested, in a gem. Drawback: I end up coding my own, identical SessionsController each time.
  • Use one of the billion other authentication libraries out there. Advantage: none. Drawback: usually not well tested, not used by many people (poor community support), and specific to someone else's problem.

You can tell from that list that I feel strongly about someone else's code being inside my application. But wait, you might be asking, don't you use Rails? Isn't Rails all about "someone else's code" in your application? At first glance, it sure appears that way, but the story is much more nuanced. Rails isn't in my application, it's used by my application.

Developer's craft systems that deal with a particular domain. The domain describes the specific data, data flow control, and visual representation of a system (i.e your 'models', 'controllers', and 'views'). Rails is foundation that speeds that process along by providing an 80% solution to a problem. The application code I write represents the 20% of the problem specific to my domain. My classes use and depend on the foundation, but Rails doesn't bubble up into my project.

Authentication is a frustrating nut to crack because it falls outside the 80/20 split. Nearly all of us need authentication, but not all of our authentication needs are the same. On the other hand, authentication isn't really part of the main domain each of us tries to describe. We're building a Dog Adoption system, a Warehouse Inventory system, or a Photo Sharing system. Those systems use authentication, but it's not the domain we're interested in describing.

The Rails core development community has wisely decided to exclude tightly coupled authentication solution (browse the web for people replacing the built-in Drupal or Django authentication systems and you'll quickly see why). The best solutions we can think up involve mixing your domain with logic that is clearly not your main concern?

If authentication isn't part of 80% solutions we all need (Rails) and it's not part of the 20% solution you're specifically crafting (Puppy Adopter Pro 2.0), then what the hell is it?

The answer is stupidly simple: if it's not your main domain, then it must be another domain. A domain that you're responsible for, sure, but not the same domain as your primary concern.

So, ignoring Puppy Adopter Pro for a moment, if we describe an authentication application what does it entail?

First, say we're managing Session resources (when we "log in" we are creating a Session resource and when we "log out" we are destroying a session resource). Rails (working with your browser) provides the model logic for Sessions. Sessions are resources that are not specifically database persisted, but we still need to expose certain manipulations (specifically create and destroy) to the external world so we'll have SessionsController. This is which is a perfectly acceptable use of the REST pattern.

But, because their persistence mechanism isn't permanent (by design), our next step is describing a permanently persisted entity with the ability to "obtain a new session object."

You might be tempted to call this entity User or Account, but that is muxing this domain with concepts from some other domain. In Puppy Adopter Pro, for example, an User entity will have attributes that are meaningless in the domain of authentication. For authentication we don't care that something has a name, driver's license number, and a relationship to one (or more) Puppy entities.

Instead of User, let's refer to this entity as an Authenticator. An Authenticator needs at least one persisted attribute to uniquely identify it. We could use id but, in addition to being unique, this attribute needs to be difficult to guess. Let's call this attribute password. In this domain, we'll allow password to be picked by a real world person. Of course, this introduces an interesting problem: two people could pick the same password. We can't tell the person they've guessed an existing password and need to pick another one (password needs to be difficult to guess!).

Instead, we need some better attribute that a person can choose, that must be unique, but doesn't need to be kept secret. Let's call this attribute login and say, that since login uniquely identifies an entity, password no longer needs to be unique (although it still needs to be hard to guess).

This is just a simple version of the authentication app you might build. We're obviously missing some features (an Authenticator can be updated in a password change, for example). Assume we have built an entire application that only does authentication. It doesn't do us any good in Puppy Adopter Pro because it's an entirely separate application. The entities don't overlap but they do touch at the Authenticator/User border.

To have them work together we could offer the authentication as RESTful webservice (essentially creating single sign on), but since we're only talking about one app that runs in the same environment we can greatly simply the process by running both authentication and our main domain concern as the same app, essentially creating a has_one/belongs_to relationship between User and Authenticator.

Even better, since I always warn people to 'be suspicious of one-to-one relationships at the data layer', we can persist the data for an Authenticator inside the users table and use Rails' aggregations to store the behavior in our Authenticator class. (For those who still tightly couple data persistence and model classes, check out it's all about behavior).

So, now we can say that a User is composed of an Authenticator object and the Authenticator object handles all the authentication behaviors.

Merb folks, of course, will be chuckling. We've come all the way down the garden path and arrived at a technique they already have (check out MerbAuth, an authentication application that runs inside your main application but mostly keeps out of your way). Merb 2/Rails 3 cannot get here fast enough. Now if only Rails ran on top of Rack and could contain tiny self-contained domain solutions inside your main application.

Oh, yes. Now it can.