[admins]
admin = pass
In default.ini add a new authentication handler under the [httpd] section so that the "authentication_handlers" list looks something like this:
authentication_handlers = {couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}
Then point the "authentication_db" variable under the [couch_httpd_auth] section to the database of your choosing like this:
authentication_db = mydb
Make sure "require_valid_user" is set to false. Also note that if you have your database running then you'll need to restart it now for the rest to work correctly.
Create the database you are going to be authenticating users from:
You should get the response {"ok":true}
Now we're going to make a basic user. Couchdb expects user passwords to be hashed using sha1, and I'm going to be using the following ruby script (called 'hash.rb') to do the hashing:
<code>
#!/usr/bin/env ruby
require 'digest/sha1'
print Digest::SHA1.hexdigest(ARGV.first)
</code>
First hash the password "mypassword":
$ruby hash.rb mypassword
should return 91dfd9ddb4198affc5c194cd8ce6d338fde470e2.
Create a user and a dummy document using the following commands
$curl -X PUT http://admin:pass@localhost:5984/mydb/myuser -d '{"type":"user", "hashed_password":"91dfd9ddb4198affc5c194cd8ce6d338fde470e2"}'
You should get a response that looks like {"ok":true,"id":"myuser","rev":"1-90e61ef93d1bf2f691f64cb423126218"}
What that command does is create a new document in our mydb database with attributes "type" and "hashed_password". The password for myuser is "mypassword", but will be stored in the database hashed in case someone gets access.
Now we need to create a new design document to handle user authentication. For simplicity I'm going to be using couchapp. Create a new couchapp called "_auth" from the command line.
$couchapp generate _auth
Create a new view called "users". This means making a _auth/views/users/map.js file. What you want to do is map all user documents to a dictionary of "password_sha", "salt, "secret", and "roles" keyed by username. Mine looks like this
<code>
function (doc) {
if (doc.type == 'user') {
emit(doc._id, {password_sha: doc.hashed_password, salt: "", secret: 'suparsecret', roles: ['user']});
}
}
</code>
We didn't use a salt in creating the hashed password, so we just include an empty string for the salt but generally passwords should be salted and that's where it's done. Also notice that we used the secret "suparsecret" that we set in the local.ini file earlier.
We also want to add some validation so that non-users' action are restricted. Create a file in _auth called validate_doc_update.js with the following code
<code>
function (newDoc, oldDoc, user) {
isAdmin = (user.roles.indexOf('_admin') != -1);
isUser = (user.roles.indexOf('user') != -1);
// must be admin or user to update any doc
if (!isAdmin || !isUser) {
throw({unauthorized: 'must be admin or user.'});
}
}
</code>
You will probably want to add more stuff to this file later.
Push the design document to the server by executing the following from within the "_auth" directory
Now if you try to do something like create a new document without being authenticated you should get an error:
{"error":"unauthorized","reason":"must be admin or user."}
Now to get authenticated. I'm using the cookies authentication method to create sessions between clients and the couchdb server. All you have to do is POST to /_session with the username and password and you will be given an authentication token to use for later requests.
First authenticate, here is the command and some of the output you should see (note the new "-v" argument to curl, it's important!)
* About to connect() to localhost port 5984 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 5984 (#0)
> POST /_session HTTP/1.1
> Host: localhost:5984
> Accept: */*
> Content-Length: 35
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 200 OK
< Set-Cookie: AuthSession=bXl1c2VyOjRBRjFGRjYyOnltRR2ir7eFNaVYMkPHmIG9VRmP; Version=1; Path=/; HttpOnly
< Server: CouchDB/0.11.0a (Erlang OTP/R13B)
< Date: Wed, 04 Nov 2009 22:08:38 GMT
< Content-Type: text/plain;charset=utf-8
< Content-Length: 12
< Cache-Control: must-revalidate
<
{"ok":true}
* Connection #0 to host localhost left intact
* Closing connection #0
The important part is the "Set-Cookie" header in the response on line 13. We're going to use that in future communications. Now we can add that new record:
$curl -vX PUT http://localhost:5984/mydb/dummy -d '{"type":"something", "info":"Some more information"}' -H "Cookie: AuthSession= bXl1c2VyOjRBRjFGRjYyOnltRR2ir7eFNaVYMkPHmIG9VRmP" -H "X-CouchDB-WWW-Authenticate: Cookie" -H "Content-Type: application/x-www-form-urlencoded"
{"ok":true,"id":"dummy","rev":"1-b2c3fd668db420b31478176059e2c7ff"}
You can also ask the server to return the authenticated user's username and roles with a GET to /_session
curl -X GET http://localhost:5984/_session -H "Cookie: AuthSession=bXl1c2VyOjRBRjFGRjYyOnltRR2ir7eFNaVYMkPHmIG9VRmP" -H "X-CouchDB-WWW-Authenticate: Cookie" -H "Content-Type: application/x-www-form-urlencoded"
{"ok":true,"name":"myuser","roles":["user"]}
Conclusion
This authentication method may not be for everyone. Some may have a problem with the fact that anyone can see any document in the database. What I've described above only prevents certain types of updates to documents i.e. writing to the database. Also the validation described above is far from perfect, which is why I suggest adding some more logic (like not allowing users to delete other user accounts). There's a good wiki on turning your couchdb into a translucent database so that the whole "anyone can see my db" is not such a big problem. Supposedly it is possible to delete a session by sending /_session a DELETE with the correct cookie header, but it hasn't worked for me so far. Anyway I Hope this helps!
Comments [1]