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.
CouchDB Cookie Authentication
Cookie-based authentication is straightforward and ideal for session-based applications like websites.
How It Works
- The client sends a username and password to CouchDB’s
_session
endpoint. - CouchDB verifies the credentials and issues a session cookie.
- 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
Use the Session Cookie
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
- Configure CouchDB to Use JWT Authentication
Modify the[chttpd]
section in yourlocal.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. - 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 thekid
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 thekid
in your JWT header matchesmy_key_id
in this configuration. - Set Required Claims (Optional)
To enforce the presence of specific claims in the JWT, configure therequired_claims
setting:[chttpd_auth] required_claims = exp, sub
This ensures that tokens must include expiration (exp
) and subject (sub
) claims. - 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);
- Authenticate Requests with the JWT
Include the generated JWT in theAuthorization
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 likeexp
(expiration) can be enforced using therequired_claims
setting. - Roles Assignment: By default, CouchDB looks for roles in the
_couchdb.roles
claim. You can specify a different claim name using theroles_claim_name
setting:[chttpd_auth] roles_claim_name = roles
This configuration tells CouchDB to use theroles
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
- An external service handles user login via OAuth2 or similar.
- The service validates the credentials and sets HTTP headers indicating the authenticated user’s identity and roles.
- 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
- 401 Unauthorized Errors: Ensure
enable_proxy_auth
is set totrue
and headers are passed correctly. - Header Mismatch: Double-check header names in the
local.ini
file and the Node.js proxy. - 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.