couchdb-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Jason Davies <ja...@jasondavies.com>
Subject Baking Cookie-Based Authentication into CouchDB
Date Wed, 29 Apr 2009 16:29:09 GMT
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


Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message