Hi all,

I'm in the finishing stages of writing a cookie-based authentication handler for CouchDB in Erlang. This is primarily going to be useful for CouchApps (apps running purely in CouchDB), but this also touches on a generic way to authenticate users via a CouchDB database, which could be adopted by the current default HTTP Basic auth handler.

I've put the code up here: http://github.com/jasondavies/couchdb/tree/master

---

Here are the set of requirements that I'm trying to fulfil:

- No server-side session state. By this, I mean the server doesn't know who is actually logged in or not, in contrast with traditional session-based login systems e.g. Django where each session is a row in the database. This is so that a cluster of CouchDB instances can be easily used without having to keep them tightly in sync (i.e. you can log in via one instance and you instantaneously are authenticated on all other instances with no delay). This is actually quite tricky to fulfil, as you can't really rely on nonces etc. for increased security - No password information in the cookie, even in the hash. This just makes it impossible for the password to ever be leaked, even if <insert hash algorithm here> is cracked. - Tamper-proof cookies. This is fairly obvious, use a secure hash algorithm with a secret server-side key to prevent tampering. - Retrieve users and their roles from a CouchDB database. This part might be better separated out so it can be used by the default (HTTP Basic auth) handler too. For now I'm implementing it as part of this cookie-based auth handler.

---

My solution fulfils these as follows:

- The cookie is of the form base64_urlsafe(username:expiry:SHA1(username:expiry:secret_key +user_salt)). - The expiry date in the auth token is an additional layer of security - basically it is set to be something like 1 hour ahead of the current time so that inactive tokens always expire at some point. For example, an attacker may gain access to a user's machine and lift the cookie auth token from the browser cache. This tries to limit this kind of attack (of course it's quite difficult to defend against an attacker having physical access). A new cookie is sent (with a new expiry date) on every request. For performance reasons I have added a +/- 0.1 hour window (10% of the timeout) so that new auth tokens are only generated every 6 minutes. - When CouchDB sees a valid auth cookie (i.e. hash and expiry date are okay) then it will retrieve the specified user from the view and set the roles appropriately for user_ctx. This means that if an admin changes a user's roles, the effects are almost immediate depending on the time it takes for a cluster to synchronise such changes. - Retrieving user auth info (i.e. roles and password): This is done using a special view "users" in a special design doc called "_design/ _auth". This view should return a structure with "roles" and "password" members, with an optional "salt" member to be used as a per- user salt. - The secret key is stored in the special "_design/_auth" design doc so that it can be easily shared by a cluster. - The per-user salt is a useful way of being able to log out all previous sessions for a user, simply by changing this to a new random value. - The cookie_authentication_handler can take an optional DbName parameter, so that you can set a particular database to be the user database for an entire CouchDB node.

---

Usage:

Edit your .ini files as follows:

1. Under [httpd] change authentication_handler to {couch_httpd, cookie_authentication_handler} or {couch_httpd, cookie_authentication_handler, "insert_userdb_here"}
2. Under [httpd_db_handlers] put:

_login = {couch_httpd_misc_handlers, handle_login_req}
_logout = {couch_httpd_misc_handlers, handle_logout_req}

or:

_login = {couch_httpd_misc_handlers, handle_login_req, "insert_userdb_here"} _logout = {couch_httpd_misc_handlers, handle_logout_req, "insert_userdb_here"}

---

Still to do:

- Use some kind of challenge/response mechanism for logging in via AJAX. At the moment the login handler just takes a plaintext username/ password combination sent via POST. I was thinking of using SRP (http://en.wikipedia.org/wiki/Secure_remote_password_protocol ), however I believe this would require state to be stored on the server, and maybe isn't appropriate for this. - Store hashes of passwords in the database. We can already do this, but we might want to send something like hash(password+password_salt) to the server, which would involve retrieving the appropriate password_salt for a given user first. - At the moment the cookie is set for Path=/ - this probably needs to be set to Path=/current_database by default, and be configurable so that it can be used by a proxy. - I need to work on making my tests more exhaustive, they're pretty minimal for the moment. - All this auth stuff should probably go into its own module, couch_httpd_auth or similar.

Any comments would be much appreciated.

Thanks,
--
Jason Davies

www.jasondavies.com

Reply via email to