For the last month or so I’ve been working on a little project using CouchDB and Go. As this is my first time using a NoSQL database, I’ve had to learn to think a little bit differently about how I interact with my data. One aspect of Couch that had me a little confused at first was how it deals with authentication and authorization.
When creating an application with a traditional RDBMS (with PostgreSQL being my favorite), I was used to handling authentication / authorization myself (usually via a third-party library) at the application level. While you can of course still do this with CouchDB, when I first came across CouchDB’s auth support, I thought “Great! I can just use Couch for everything!”. Turns out, I sorta can, but it’s not as simple as I had first assumed. Oh well 🙂
Users are stored in a special database called “_users” and creating a new one is as simple as a PUT request (Admin credentials are generally required for creating or deleting users). For authentication, you can either use Couch’s Session API, or in the case of HTTP Basic, just pass the client’s Basic Authentication headers through to Couch from your application. This was easy enough to implement and seems to work well enough for what I need.
Authorization, however, is where things can get tricky. CouchDB handles permissions on a per database basis. It is not possible to restrict access to individual documents. “Well, that’s useless, guess I’ll have to handle authorization some other way” I thought to myself. But the answer, if your use-case allows for it, is just to create multiple databases at the level of granularity you need to control access. I resisted doing this at first, as being used to RDBMS systems, I kept thinking “a database is a big deal and creating dozens, hundreds, or even thousands of them is insane.” But CouchDB is not Postgres 🙂 and a little research told me that creating a database per user account is actually a very common use case and doesn’t cause any serious issues (if you start getting into thousands of databases you may need to tweak your config to allow a larger number of databases to be open simultaneously).
CouchDB allows you to grant access to databases either on a per-user basis or based on user roles. I would highly recommend doing role-based authentication as I think that’s more flexible. To specify database users/roles, you edit a database’s security document (called ‘_security’). The structure of the security object looks like this:
{ "admins": { "names": [ "superuser" ], "roles": [ "admins" ] }, "members": { "names": [ "user1", "user2" ], "roles": [ "developers" ] } }
CouchDB allows two different levels of user: ‘members’ and ‘admins.’ Members can read and write documents in the database while admins can edit design documents as well as the database security document. One thing to note here is that if no users/roles are specified, CouchDB will default a database to ‘public’ access, so… definitely put something here. If you’re using roles, in order to grant/revoke access to a user, simply add or remove the desired role to/from the roles array from the user’s document in the _users database.
Now, CouchDB allows you to create and assign any roles you want, but doesn’t enforce any permissions beyond the aforementioned ‘member’ and ‘admin’ types. So, since members can both read and write documents, how can you create roles for read-only users? Unfortunately, CouchDB doesn’t make this easy or obvious, but it does provide a way to make it work.
I’ll illustrate how to do this with an example: Let’s say you have an application that’s a photo sharing service. Each user has various ‘albums’ that they can either keep private or share with others. Most of the time, users are going to want to give others ‘read-only’ access to an album. So, every time a user creates a new album, we create a database for it. When a database is created, we also make sure to configure it with three roles: one admin role and two member roles, like so:
{ "admins": { "names": [], "roles": [ "stan_vacation_album:admin" ] }, "members": { "names": [], "roles": [ "stan_vacation_album:read", "stan_vacation_album:write" ] } }
So, Stan has a vacation album. Let’s say he wants to grant read access to his friend Bill. He tells your app to grant read access to Bill and your app turns around and edit’s Bill’s user document by adding “stan_vacation_album:read” to his list of roles. Great! Bill has access, but how do we enforce read-only access and prevent Bill from, say, using Photoshop to edit himself into Stan’s vacation photos (Bill is not a good friend) and saving them over the originals in Stan’s database?
You can create a special design document in each database called ‘_auth’. Within that design document, you can specify a function called validate_doc_update, which will be triggered each time someone tries to save a new revision of a document. So, all we need to do is check the user’s role and not allow the update to go through if the user doesn’t have the right ‘role.’ For example:
function(newDoc, oldDoc, userCtx){ if((userCtx.roles.indexOf('stan_vacation_album:write') == -1)&& (userCtx.roles.indexOf('stan_vacation_album:admin') == -1)&& (userCtx.roles.indexOf('_admin') == -1)){ throw({forbidden: "Not Authorized"}); } }
is a quick and dirty function that would do the trick. Anyone that isn’t an admin or doesn’t have the :write role, won’t be able to write to the database. Of course you can also do any data validation you like here as well.
Hope this helps 🙂
I’m also developing a CouchDB driver for Go as I work on my project, feel free to use it however you like.
One thought on “CouchDB Authorization”