wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

CouchDB and JWKS certificates


Depending on how deep you authenticate, you might be tasked maintaining a user base in _users (and welcome to "I forgot my password" hell). The standing recommendation is to implement a single source of identity using a directory as Identity Provider (IdP). My favorite NoSQL database can be configured to trust JWT signed by known IdPs, so let's do that.

Some assembly required

CouchDB can be configured in three ways: Edit the respective ini file, use the Fauxton UI or use the REST API. I like the later since I'm comfortable with curl and Bruno (not a fan of Postman anymore). The steps are:

  • configure a client on your identity provider
  • enable JWT authentication
  • specify what claims are mandatory
  • specify how to map roles
  • add trustedd public keys
  • restart your node

Identity provider

I covered one-off Keycloak before, but there's one modified step you want to take. CouchDB uses the sub as username. I added a mapper that delivers the email as value.

curl -X POST ${KEYCLOAK}/admin/realms/${REALM}/clients \
  --header "authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" \
  --header 'content-type: application/json' \
  --data '{
    "clientId": "fauxton",
    "name" : "CouchDB Fauxton",
    "enabled": true,
    "publicClient": true,
    "directAccessGrantsEnabled": true,
    "redirectUris":["http://localhost:5984/"],
    "webOrigins": ["localhost:5984"],
    "protocolMappers": [
        {
          "name": "email to sub",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-property-mapper",
          "consentRequired": false,
          "config": {
              "introspection.token.claim": "true",
              "userinfo.token.claim": "true",
              "user.attribute": "email",
              "id.token.claim": "true",
              "lightweight.claim": "false",
              "access.token.claim": "true",
              "claim.name": "sub",
              "jsonType.label": "String"
            }
        }
    ]
}

CouchDB setup

Check your setup carefully, to ensure you do send what fits in your environment. Keep in mind, any ${something} are environment variables that need to be set. \ is a line continuation on macOS or Linux. If you are on Windows, shell into your CouchDB container or use WSL.

# Enable JWT auth
curl -u ${COUCHDB_USRPWD} -X PUT "http://localhost:5984/_node/_local/_config/chttpd/authentication_handlers" \
-H "Content-Type: text/plain" \
-d '"{chttpd_auth, cookie_authentication_handler}, {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, default_authentication_handler}"'

# Define required claims
curl -u ${COUCHDB_USRPWD} -X PUT "http://localhost:5984/_node/_local/_config/jwt_auth/required_claims" \
  --header 'content-type: text/plain' \
  --data "\"exp,iat\""

# Set path for role resolution (keycloak style)
curl -u ${COUCHDB_USRPWD} -X PUT "http://localhost:5984/_node/nonode@nohost/_config/jwt_auth/roles_claim_path" \
-H "Content-Type: text/plain" \
-d "\"realm_access.roles\""

The trouble with the keys

OIDC compliant IdPs offer JSON Web Key Sets(JWKS) for JWK validation. You visit the IdP's /.well-known/openid-configuration URL and retrieve the jwks_uri element wich points to the keys.

CouchDB doesn't support JWKS, but needs public keys in PEM format collapsed into a single line string with line feeds turned into \n. CouchDB uses the kid to match a JWT efficiently to the key to validate. Converting JWKS to PEM is beyond (my) shell script skills, so I resorted to NodeJS. After sufficiently torturing Claude, I got a script that does that job, go check it out.

node jwks2couch.mjs

Restart the CouchDB node

curl -u ${COUCHDB_USRPWD} -X POST "http://localhost:5984/_node/_local/_restart"

Now you can test access using a JWT

curl http://localhost:5984/_session \
  --header "Authorization: Bearer ${TOKEN}"

Final thoughts

IdPs change and roll-over keys as regular operation. Once this happens CouchDB authentication will fail until you re-import the IdP keys. To make this fire and forget, I created the CouchDB IdP updater. Go and check it out!

As usual YMMV


Posted by on 30 July 2025 | Comments (0) | categories: CouchDB JWT

Comments

  1. No comments yet, be the first to comment