MDDB Authentication & Authorization
Complete guide to JWT authentication and RBAC (Role-Based Access Control) in MDDB.
What's new in 2.9.15. Authentication is now compliance-aware:
- Timing-safe error unification โ "user disabled or not found" now returns the same
invalid tokenresponse as a bad JWT, so an attacker can no longer probe user existence via differential error messages (services/mddbd/auth_middleware.go).- Audit trail of every auth event โ every login, JWT verification, API-key check, and missing/invalid/disabled attempt is recorded to a dedicated audit bucket with actor, IP, user agent, and outcome. Enable with
MDDB_AUDIT_ENABLED=trueand query via admin-onlyGET /v1/audit.security.auth_failure_burstincident event โ the newAuthFailureTrackerintegrates with the auth middleware. When the configured number of failures lands from the sameactor@ipinside the sliding window (MDDB_INCIDENT_AUTH_*), MDDB fires a webhook to every subscriber on/v1/webhookswith detail{actor, ip, count, windowSec}so your SIEM / PagerDuty / Slack receives an alert without polling.See SECURITY.md for the compliance map and config.md for every related environment variable.
Table of Contents
- Overview
- Quick Start
- Authentication Methods
- Authorization (RBAC)
- Environment Variables
- API Endpoints
- Client Configuration
- Security Best Practices
- Testing
- Troubleshooting
Overview
MDDB supports optional authentication and authorization to secure your markdown database. Key features:
- Disabled by default - Opt-in via
MDDB_AUTH_ENABLED=true - JWT tokens - Stateless authentication with configurable expiry
- API keys - Long-lived credentials for services and CI/CD
- RBAC - Per-collection read/write/admin permissions
- BoltDB storage - Auth data stored alongside your documents
- All protocols - Works with HTTP and gRPC
Quick Start
1. Enable Authentication
Start MDDB with authentication enabled:
cd services/mddbd
MDDB_AUTH_ENABLED=true \
MDDB_AUTH_JWT_SECRET=$(openssl rand -hex 32) \
MDDB_AUTH_ADMIN_USERNAME=admin \
MDDB_AUTH_ADMIN_PASSWORD=changeme \
go run .
2. Login
curl http://localhost:11023/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"changeme"}'
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": 1772563099
}
3. Use Token
TOKEN="your-token-here"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:11023/v1/stats
Authentication Methods
1. JWT Tokens (Username + Password)
Best for: Interactive users, web applications
Login to receive a JWT token:
curl http://localhost:11023/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "changeme"
}'
Use the token in subsequent requests:
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://localhost:11023/v1/add \
-d '{
"collection": "docs",
"key": "readme",
"lang": "en",
"contentMd": "# Welcome"
}'
Token Properties:
- Default expiry: 24 hours (configurable)
- Algorithm: HS256
- Contains: username, admin flag, expiry
2. API Keys
Best for: Services, CI/CD pipelines, MCP servers
Create an API key:
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://localhost:11023/v1/auth/api-key \
-H "Content-Type: application/json" \
-d '{
"description": "CI server",
"expiresAt": 0
}'
Response:
{
"key": "mddb_live_b9a2604ba923ea920451d139a5f366eb384434600aff7e1d",
"description": "CI server",
"expiresAt": 0,
"createdAt": 1772476811
}
โ ๏ธ Important: The full API key is shown only once! Save it securely.
Use API key:
curl -H "X-API-Key: mddb_live_..." \
http://localhost:11023/v1/stats
API Key Format:
- Prefix:
mddb_live_ - Length: 48 hex characters (24 bytes of randomness)
- Storage: SHA256 hash
- Expiry: Optional (0 = never expires)
Authorization (RBAC)
MDDB implements Role-Based Access Control with per-collection permissions.
Permission Types
| Permission | Operations | Description |
|---|---|---|
| read | Get, Search, Export, FTS | View documents |
| write | Add, Update, Delete, Import | Modify documents |
| admin | Backup, Restore, Stats, User management | Database operations |
Granting Permissions
Only admins can grant permissions:
curl -H "Authorization: Bearer ADMIN_TOKEN" \
http://localhost:11023/v1/auth/permissions \
-H "Content-Type: application/json" \
-d '{
"username": "alice",
"collection": "blog",
"read": true,
"write": false,
"admin": false
}'
Wildcard Collection
Use "*" to grant database-wide permissions:
{
"username": "bob",
"collection": "*",
"read": true,
"write": true,
"admin": false
}
Permission Lookup Order
- Check if user is admin (bypasses all checks)
- Check collection-specific permission
- Check wildcard (
"*") permission - Deny by default
Example Scenarios
Scenario 1: Read-only user for analytics
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:11023/v1/auth/register \
-d '{"username":"analyst","password":"view123"}'
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:11023/v1/auth/permissions \
-d '{
"username": "analyst",
"collection": "*",
"read": true,
"write": false,
"admin": false
}'
Scenario 2: Collection-specific editor
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:11023/v1/auth/register \
-d '{"username":"editor","password":"edit123"}'
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:11023/v1/auth/permissions \
-d '{
"username": "editor",
"collection": "articles",
"read": true,
"write": true,
"admin": false
}'
Environment Variables
| Variable | Default | Required | Description |
|---|---|---|---|
MDDB_AUTH_ENABLED |
false |
No | Enable authentication |
MDDB_AUTH_JWT_SECRET |
- | Yes (if auth enabled) | Secret for JWT signing (use strong random value) |
MDDB_AUTH_JWT_EXPIRY |
24h |
No | JWT token expiration (e.g., 1h, 7d) |
MDDB_AUTH_ADMIN_USERNAME |
admin |
No | Bootstrap admin username |
MDDB_AUTH_ADMIN_PASSWORD |
- | Yes (if auth enabled) | Bootstrap admin password |
Example Configuration
Development:
export MDDB_AUTH_ENABLED=true
export MDDB_AUTH_JWT_SECRET=$(openssl rand -hex 32)
export MDDB_AUTH_ADMIN_USERNAME=admin
export MDDB_AUTH_ADMIN_PASSWORD=dev123
Production (Docker):
docker run -d \
-e MDDB_AUTH_ENABLED=true \
-e MDDB_AUTH_JWT_SECRET=your-secret-key-here \
-e MDDB_AUTH_ADMIN_USERNAME=admin \
-e MDDB_AUTH_ADMIN_PASSWORD=secure-password \
-v /data:/data \
mddb:latest
API Endpoints
Authentication Endpoints
POST /v1/auth/login
Login with username and password.
Request:
{
"username": "admin",
"password": "changeme"
}
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": 1772563099
}
POST /v1/auth/register
Create new user (admin only).
Request:
{
"username": "alice",
"password": "secret123"
}
Response:
{
"username": "alice",
"createdAt": 1772476836
}
POST /v1/auth/api-key
Generate API key (authenticated users).
Request:
{
"description": "CI server",
"expiresAt": 0
}
Response:
{
"key": "mddb_live_b9a2604ba923ea920451d139a5f366eb...",
"description": "CI server",
"expiresAt": 0,
"createdAt": 1772476811
}
GET /v1/auth/me
Get current user information.
Response:
{
"username": "admin",
"admin": true,
"createdAt": 1772476700
}
Authorization Endpoints (Admin Only)
POST /v1/auth/permissions
Set user permissions.
Request:
{
"username": "alice",
"collection": "blog",
"read": true,
"write": false,
"admin": false
}
GET /v1/auth/permissions?username=alice
Get user permissions.
Response:
[
{
"username": "alice",
"collection": "blog",
"read": true,
"write": false,
"admin": false
}
]
DELETE /v1/auth/users/:username
Delete user (admin only).
Response:
{
"status": "deleted"
}
Public Endpoints (No Authentication Required)
GET /healthGET /v1/healthPOST /v1/auth/loginGET /metrics
Client Configuration
CLI (mddb-cli)
Using JWT Token
mddb-cli login admin changeme
mddb-cli --token YOUR_TOKEN stats
mddb-cli --token YOUR_TOKEN add docs readme en -f README.md
Using API Key
mddb-cli --api-key mddb_live_... stats
MCP Server (mddb-mcp)
config.yaml
mddb:
grpcAddress: "localhost:11024"
restBaseURL: "http://localhost:11023/v1"
transportMode: "rest-only"
timeout: 30s
apiKey: "mddb_live_your-api-key-here" # Add this line
server:
httpPort: 8080
enableHTTP: true
enableStdio: true
Environment Variable
export MDDB_API_KEY=mddb_live_...
mddb-mcp --config config.yaml
Panel (React UI)
The Panel automatically detects if authentication is enabled:
- On first load, attempts to access
/v1/stats - If
401 Unauthorized, shows login form - Stores JWT token in
localStorage - Includes
Authorization: Bearer TOKENin all requests - Logout button clears token and reloads page
No configuration needed!
Security Best Practices
1. Use Strong JWT Secret
Generate a cryptographically secure secret:
openssl rand -hex 32
Never use a weak or default secret in production!
2. Change Default Admin Password
Immediately after first deployment:
TOKEN=$(curl -s http://localhost:11023/v1/auth/login \
-d '{"username":"admin","password":"changeme"}' | jq -r .token)
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:11023/v1/auth/register \
-d '{"username":"newadmin","password":"strong-password-here"}'
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:11023/v1/auth/permissions \
-d '{
"username": "newadmin",
"collection": "*",
"read": true,
"write": true,
"admin": true
}'
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
http://localhost:11023/v1/auth/users/admin
3. Principle of Least Privilege
Grant minimum permissions needed:
- Read-only for analytics/reporting
- Collection-specific for editors
- Admin only for trusted operators
4. Rotate API Keys Periodically
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:11023/v1/auth/api-key \
-d '{
"description": "Q1 2026 CI key",
"expiresAt": 1735689600
}'
5. Use HTTPS in Production
Never send credentials over unencrypted HTTP in production. Use a reverse proxy like nginx or Caddy:
server {
listen 443 ssl;
server_name mddb.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:11023;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
6. Monitor Authentication Logs
Watch for suspicious activity:
tail -f /var/log/mddb.log | grep "authentication failed"
7. Secure Environment Variables
In production:
- Use Docker secrets or Kubernetes secrets
- Never commit
.envfiles with real credentials - Rotate secrets regularly
Testing
Automated Tests
Run the comprehensive test suite:
./test-auth.sh
./test-mcp.sh
./test-panel.sh
Manual Testing
1. Test Login Flow
curl http://localhost:11023/v1/stats
TOKEN=$(curl -s http://localhost:11023/v1/auth/login \
-d '{"username":"admin","password":"changeme"}' | jq -r .token)
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:11023/v1/stats
2. Test RBAC
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:11023/v1/auth/register \
-d '{"username":"readonly","password":"view123"}'
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:11023/v1/auth/permissions \
-d '{
"username": "readonly",
"collection": "docs",
"read": true,
"write": false
}'
RO_TOKEN=$(curl -s http://localhost:11023/v1/auth/login \
-d '{"username":"readonly","password":"view123"}' | jq -r .token)
curl -H "Authorization: Bearer $RO_TOKEN" \
http://localhost:11023/v1/search \
-d '{"collection":"docs","limit":10}'
curl -H "Authorization: Bearer $RO_TOKEN" \
http://localhost:11023/v1/add \
-d '{"collection":"docs","key":"test","lang":"en","contentMd":"# Test"}'
Troubleshooting
Problem: "missing authentication" error
Cause: No token provided or public endpoint misconfigured.
Solution:
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:11023/v1/stats
Problem: "invalid token" error
Cause: Token expired or JWT secret mismatch.
Solution:
- Check token expiry:
echo $TOKEN | base64 -d | jq .exp - Login again to get fresh token
- Verify
MDDB_AUTH_JWT_SECREThasn't changed
Problem: "forbidden" error (403)
Cause: User lacks permissions for the operation.
Solution:
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://localhost:11023/v1/auth/permissions?username=alice"
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:11023/v1/auth/permissions \
-d '{
"username": "alice",
"collection": "docs",
"read": true,
"write": true
}'
Problem: Can't login as admin
Cause: Admin user not created or wrong password.
Solution:
-
Check environment variables:
echo $MDDB_AUTH_ADMIN_USERNAME echo $MDDB_AUTH_ADMIN_PASSWORD -
Check server logs for bootstrap admin creation:
tail -f /var/log/mddb.log | grep "bootstrap admin" -
If needed, recreate database:
rm mddb.db # Restart server to recreate bootstrap admin
Problem: MCP service can't authenticate
Cause: API key not configured.
Solution:
mddb:
apiKey: "mddb_live_your-key-here"
Or use environment variable:
export MDDB_API_KEY=mddb_live_your-key-here
Storage Details
BoltDB Buckets
Authentication data is stored in three BoltDB buckets:
-
auth_users
- Key:
user|{username} - Value: JSON with username, passwordHash (bcrypt), createdAt, disabled
- Key:
-
auth_apikeys
- Key:
apikey|{keyHash} - Value: JSON with keyHash (SHA256), username, createdAt, expiresAt, description
- Key:
-
auth_permissions
- Key:
perm|{username}|{collection} - Value: JSON with collection, read, write, admin flags
- Key:
Password Hashing
- Algorithm: bcrypt
- Cost factor: 12
- Salt: Automatically generated per password
API Key Generation
- Random bytes: 24 (crypto/rand)
- Encoding: Hex (48 characters)
- Prefix:
mddb_live_ - Storage: SHA256 hash (for constant-time comparison)
Migration Guide
From No Auth to Auth Enabled
-
Plan migration window (brief downtime required)
-
Backup database:
cp mddb.db mddb.db.backup -
Enable auth:
export MDDB_AUTH_ENABLED=true export MDDB_AUTH_JWT_SECRET=$(openssl rand -hex 32) export MDDB_AUTH_ADMIN_USERNAME=admin export MDDB_AUTH_ADMIN_PASSWORD=changeme -
Restart MDDB
-
Create users and API keys for all services
-
Update all clients (CLI, MCP, Panel, custom apps)
-
Test thoroughly before promoting to production
Rolling Back
If you need to disable auth:
export MDDB_AUTH_ENABLED=false
Auth data (users, keys, permissions) remains in database but is not enforced.
Support
For issues or questions:
- GitHub Issues: https://github.com/anthropics/mddb/issues
- Documentation: https://github.com/anthropics/mddb/docs
- Email: [email protected]
Last Updated: March 2, 2026 MDDB Version: 2.3.3+