Mastering Authentication in CouchDB: From Basics to Advanced Integration

Authentication is a cornerstone of any secure web application. CouchDB, known for its flexibility and simplicity, provides developers with robust tools for managing user authentication. In this article, we’ll explore the essentials of CouchDB’s authentication system, starting with cookie-based authentication, progressing to JWT tokens for stateless security, and concluding with external provider integrations for modern authentication flows. By the end, you’ll be equipped to confidently secure your CouchDB-powered applications.


Introduction to CouchDB Authentication

CouchDB offers a variety of built-in authentication mechanisms tailored to different needs:

  • Cookie-based authentication for session-based management.
  • JWT tokens for stateless, scalable security.
  • External authentication integration for using OAuth2 or OpenID Connect.

These options make CouchDB a versatile choice for applications of any size. Let’s dive into each method, starting with cookie-based authentication.


Cookie-based authentication is straightforward and ideal for session-based applications like websites.

How It Works

  1. The client sends a username and password to CouchDB’s _session endpoint.
  2. CouchDB verifies the credentials and issues a session cookie.
  3. The client uses this cookie for subsequent requests.

Step-by-Step Implementation

Enable Authentication in CouchDB

Ensure that CouchDB’s [chttpd] section has authentication enabled:

[chttpd]
authentication_handlers = {couch_httpd_auth, cookie_authentication_handler}

Restart CouchDB after making changes:

sudo systemctl restart couchdb

Authenticate a User

Send a POST request to the _session endpoint:

curl -X POST http://localhost:5984/_session \
     -H "Content-Type: application/json" \
     -d '{"name": "username", "password": "password"}'
Response Details

CouchDB will return the following:

  • JSON body indicating success:
    {
      "ok": true,
      "name": "username",
      "roles": []
    }
    
  • Cookie headers in the HTTP response:
    Set-Cookie: AuthSession=<session_token>; Version=1; Path=/; HttpOnly
    

Include the session cookie (AuthSession) in subsequent requests:

curl -X GET http://localhost:5984/my_database \
     -H "Cookie: AuthSession=<session_token>"

Use Cases

  • Web applications requiring a stateful session.
  • Applications with infrequent logins.

Implementing JWT Authentication in CouchDB

Overview

JSON Web Tokens (JWT) offer a stateless authentication mechanism, ideal for scalable applications. CouchDB supports JWT authentication by verifying tokens signed with trusted keys specified in the [jwt_keys] section of its configuration.

Step-by-Step Implementation

  1. Configure CouchDB to Use JWT Authentication
    Modify the [chttpd] section in your local.ini to include the JWT authentication handler:
    [chttpd]
    authentication_handlers = {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, default_authentication_handler}
    

    This configuration ensures that CouchDB recognizes and processes JWTs for authentication.
  2. Define Trusted JWT Keys
    In the [jwt_keys] section, specify the keys that CouchDB should trust for verifying JWT signatures. Each key is identified by a unique key ID (kid), which should match the kid claim in the JWT header.
    [jwt_keys]
    ; For symmetric keys, the value is base64 encoded;
    ; hmac:_default = aGVsbG8= # base64-encoded form of "hello"
    hmac:_default = aGVsbG8=
    ; For asymmetric keys, the value is the PEM encoding of the public
    ; key with newlines replaced with the escape sequence \n.
    ; rsa:foo = -----BEGIN PUBLIC KEY-----\nMIIBIjAN...IDAQAB\n-----END PUBLIC KEY-----\n
    

    Ensure that the kid in your JWT header matches my_key_id in this configuration.
  3. Set Required Claims (Optional)
    To enforce the presence of specific claims in the JWT, configure the required_claims setting:
    [chttpd_auth]
    required_claims = exp, sub
    

    This ensures that tokens must include expiration (exp) and subject (sub) claims.
  4. Generate a JWT

Use a library like jsonwebtoken in Node.js to generate a token:

const jwt = require('jsonwebtoken');
const token = jwt.sign({ name: "username", roles: ["user"] }, "your-secret-key", { algorithm: "HS256" });
console.log(token);
  1. Authenticate Requests with the JWT
    Include the generated JWT in the Authorization header for your HTTP requests to CouchDB:
    curl -X GET http://localhost:5984/my_database \
         -H "Authorization: Bearer <jwt_token>"
    

    Replace <jwt_token> with the actual token generated in the previous step.

Important Considerations

  • Algorithm Support: CouchDB supports various algorithms for JWTs, including HS256. Ensure that the algorithm used in token generation matches CouchDB's configuration.
  • Claim Requirements: The sub claim is mandatory and is used as the CouchDB username. Additional claims like exp (expiration) can be enforced using the required_claims setting.
  • Roles Assignment: By default, CouchDB looks for roles in the _couchdb.roles claim. You can specify a different claim name using the roles_claim_name setting:
    [chttpd_auth]
    roles_claim_name = roles
    

    This configuration tells CouchDB to use the roles claim for assigning user roles.

Troubleshooting Tips

  • Invalid Signature Errors: Ensure that the kid in the JWT header matches an entry in the [jwt_keys] section and that the corresponding secret key is correct.
  • Missing Claims: If CouchDB rejects a token due to missing claims, verify that all required claims are present and correctly named in the token payload.
  • Token Expiry: If using the exp claim, ensure that the token is used before it expires. Expired tokens will be rejected by CouchDB.

Proxy Authentication for External OAuth Providers

While CouchDB no longer natively supports OAuth as of version 2.1, you can still integrate external providers like Google or GitHub using Proxy Authentication. This method allows you to offload authentication to an external service while CouchDB relies on headers set by a trusted proxy.

Why Use Proxy Authentication?

  • Offload authentication logic to external providers like Google, GitHub, or any OAuth-compliant service.
  • Simplify user management and enhance security by leveraging well-tested third-party authentication.

How Proxy Authentication Works

  1. An external service handles user login via OAuth2 or similar.
  2. The service validates the credentials and sets HTTP headers indicating the authenticated user’s identity and roles.
  3. CouchDB trusts these headers, treating the user as authenticated.

Step-by-Step Implementation

1. Enable Proxy Authentication in CouchDB

Modify your local.ini file to enable proxy authentication:

[chttpd_auth]
authentication_handlers = {chttpd_auth, proxy_authentication_handler}

[httpd]
enable_proxy_auth = true

[couch_httpd_auth]
proxy_use_secret = true
x_auth_username = X-Auth-CouchDB-User
x_auth_roles = X-Auth-CouchDB-Roles
x_auth_token = X-Auth-CouchDB-Token

Restart CouchDB:

sudo systemctl restart couchdb

2. Implement an OAuth Proxy in Node.js

Use Node.js to handle the OAuth workflow and forward authenticated requests to CouchDB.

Install Dependencies:

npm install express passport passport-google-oauth20 axios

Sample Node.js OAuth Proxy:

const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const axios = require('axios');

const app = express();
const PORT = 3000;

// Configure Google OAuth
passport.use(new GoogleStrategy({
    clientID: 'YOUR_GOOGLE_CLIENT_ID',
    clientSecret: 'YOUR_GOOGLE_CLIENT_SECRET',
    callbackURL: '/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
  done(null, profile);
}));

// Serialize user
passport.serializeUser((user, done) => {
  done(null, user);
});

passport.deserializeUser((user, done) => {
  done(null, user);
});

// Initialize Passport
app.use(passport.initialize());

// Google OAuth Routes
app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);

app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/' }),
    (req, res) => {
        // Extract user info and forward as headers to CouchDB
        const username = req.user.displayName || req.user.emails[0].value;
        const roles = ['user']; // Define roles based on your app logic
        
            // Forward request to CouchDB
            axios.get('http://localhost:5984/_all_dbs', {
              headers: {
                'X-Auth-CouchDB-User': username,
                'X-Auth-CouchDB-Roles': roles.join(','),
                'X-Auth-CouchDB-Token': 'dummy-token' // Optional if token validation is needed
              }
            }).then(couchRes => {
              res.json({ couchDBResponse: couchRes.data });
            }).catch(err => {
              res.status(err.response?.status || 500).json({ error: err.message });
            });
    }
);

app.listen(PORT, () => {
  console.log(`OAuth proxy running on http://localhost:${PORT}`);
});

3. Forward Requests to CouchDB

After successful authentication, the proxy sets the following headers in requests to CouchDB:

  • X-Auth-CouchDB-User: The authenticated username.
  • X-Auth-CouchDB-Roles: A comma-separated list of user roles.
  • X-Auth-CouchDB-Token: (Optional) An additional token for verifying proxy authenticity.

CouchDB will trust these headers to authenticate users, eliminating the need for CouchDB to handle OAuth directly.

Use Cases

  • Applications requiring social logins or delegated authentication.
  • Enterprises managing authentication through centralized identity providers.

Troubleshooting Tips

  1. 401 Unauthorized Errors: Ensure enable_proxy_auth is set to true and headers are passed correctly.
  2. Header Mismatch: Double-check header names in the local.ini file and the Node.js proxy.
  3. SSL Issues: Use HTTPS for secure communication between the client, proxy, and CouchDB.

Key Takeaways

  • CouchDB offers flexible authentication: cookies for simplicity, JWTs for scalability, and external providers for modern flows.
  • Always secure CouchDB with SSL and robust authentication configurations.
  • Experiment with these methods to determine what works best for your application.