7.4 KiB
Authentication Setup - Mem0 Memory Server
✅ Implementation Complete
A simple but effective API key-based authentication layer has been added to ensure users can only access their own memories.
🔐 How It Works
1. API Key to User Mapping
API keys are mapped to user IDs in the .env file:
API_KEYS='{"sk-alice-test-key-12345": "alice", "sk-bob-test-key-67890": "bob", "sk-carol-test-key-abcdef": "carol"}'
2. Authentication Flow
- Client sends request with
X-API-Keyheader - Backend validates API key against configured mapping
- Backend extracts authenticated user_id
- Backend verifies user can only access their own data
- Request proceeds or 401/403 error returned
3. Protected Endpoints
All memory-related endpoints now require authentication:
| Endpoint | Method | Auth Required | User Isolation |
|---|---|---|---|
/chat |
POST | ✅ | ✅ User can only chat as themselves |
/memories |
POST | ✅ | ✅ User can only add to own memories |
/memories/search |
POST | ✅ | ✅ User can only search own memories |
/memories/{user_id} |
GET | ✅ | ✅ User can only view own memories |
/memories |
PUT | ✅ | ✅ User can only update own memories |
/memories/{memory_id} |
DELETE | ✅ | ✅ User can only delete own memories |
/memories/user/{user_id} |
DELETE | ✅ | ✅ User can only delete own memories |
/graph/relationships/{user_id} |
GET | ✅ | ✅ User can only view own relationships |
/stats/{user_id} |
GET | ✅ | ✅ User can only view own stats |
/health |
GET | ❌ | N/A (Public) |
/stats |
GET | ❌ | N/A (Global stats) |
/models |
GET | ❌ | N/A (Public) |
📝 Configuration
Environment Setup
1. Update .env file:
# Add API key mapping
API_KEYS='{"api_key_1": "user1", "api_key_2": "user2"}'
2. Generate Secure API Keys (Production):
# Generate a random 32-character API key
openssl rand -hex 32
# Example output: sk-a1b2c3d4e5f6...
3. Restart Services:
docker-compose down
docker-compose up -d
🧪 Testing Authentication
Test 1: No API Key (Should Fail - 403)
curl -X POST "http://localhost:8000/memories" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Test"}],"user_id":"alice"}'
# Response: {"detail":"Not authenticated"}
Test 2: Invalid API Key (Should Fail - 401)
curl -X POST "http://localhost:8000/memories" \
-H "Content-Type: application/json" \
-H "X-API-Key: invalid-key" \
-d '{"messages":[{"role":"user","content":"Test"}],"user_id":"alice"}'
# Response: {"detail":"Invalid API key"}
Test 3: Valid API Key (Should Succeed - 200)
curl -X POST "http://localhost:8000/memories" \
-H "Content-Type: application/json" \
-H "X-API-Key: sk-alice-test-key-12345" \
-d '{"messages":[{"role":"user","content":"My name is Alice"}],"user_id":"alice"}'
# Response: {"added_memories":[...], "message":"Memories added successfully"}
Test 4: Cross-User Access (Should Fail - 403)
# Alice trying to access Bob's data
curl -X POST "http://localhost:8000/memories" \
-H "Content-Type: application/json" \
-H "X-API-Key: sk-alice-test-key-12345" \
-d '{"messages":[{"role":"user","content":"Test"}],"user_id":"bob"}'
# Response: {"detail":"Access denied: You can only add memories for yourself (authenticated as 'alice')"}
Test 5: Authenticated Chat
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-H "X-API-Key: sk-alice-test-key-12345" \
-d '{"message":"What do you know about me?","user_id":"alice"}'
# Response: {"response":"Based on your memories...", "memories_used":5, ...}
Test 6: Authenticated Search
curl -X POST "http://localhost:8000/memories/search" \
-H "Content-Type: application/json" \
-H "X-API-Key: sk-bob-test-key-67890" \
-d '{"query":"tennis","user_id":"bob"}'
# Response: {"memories":[...], "total_count":4}
🔧 Implementation Details
Files Modified/Created:
-
backend/auth.py(NEW)AuthServiceclass for API key validationget_current_user()FastAPI dependencyverify_user_access()for cross-user protection
-
backend/config.py(UPDATED)- Added
api_keysfield using proper Pydantic V2 syntax - Added
api_key_mappingproperty to parse JSON - Uses
validation_aliaswithAliasChoicesfor env var mapping
- Added
-
backend/main.py(UPDATED)- All protected endpoints now use
authenticated_user = Depends(get_current_user) - Added user isolation checks in each endpoint
- All protected endpoints now use
-
docker-compose.yml(UPDATED)- Added
API_KEYSenvironment variable mapping
- Added
-
.env(UPDATED)- Added
API_KEYSconfiguration
- Added
-
.env.example(UPDATED)- Added
API_KEYStemplate with security notes
- Added
🎯 Security Features Implemented
✅ API Key Authentication: Every protected request requires valid API key
✅ User Isolation: Users can only access their own data
✅ Cross-User Protection: Prevents user A from accessing user B's memories
✅ Structured Logging: All auth events logged with correlation IDs
✅ Environment-Based Config: API keys stored in .env (not hardcoded)
✅ Clear Error Messages: Informative 401/403 responses
🔒 Production Recommendations
Current Setup (✅ Completed)
- ✅ API key authentication
- ✅ User isolation
- ✅ Environment-based configuration
- ✅ Cross-user access protection
Future Enhancements (Optional)
- Move API keys to secrets manager (AWS Secrets Manager, HashiCorp Vault)
- Add API key rotation mechanism
- Add rate limiting per API key
- Add API key expiration
- Add audit logging for security events
- Add API key scopes/permissions
- Add HTTPS enforcement
- Add request signing for extra security
📊 Test Results
All authentication tests PASSED ✅
| Test | Result | HTTP Status |
|---|---|---|
| No API key | ✅ Blocked | 403 |
| Invalid API key | ✅ Blocked | 401 |
| Valid API key | ✅ Allowed | 200 |
| Cross-user access | ✅ Blocked | 403 |
| Authenticated chat | ✅ Works | 200 |
| Authenticated search | ✅ Works | 200 |
| Stats isolation | ✅ Blocked | 403 |
💡 Usage Example
import requests
# Configure API key
API_KEY = "sk-alice-test-key-12345"
BASE_URL = "http://localhost:8000"
headers = {
"Content-Type": "application/json",
"X-API-Key": API_KEY
}
# Add memory
response = requests.post(
f"{BASE_URL}/memories",
headers=headers,
json={
"messages": [{"role": "user", "content": "I love Python programming"}],
"user_id": "alice"
}
)
print(response.json())
# {"added_memories": [...], "message": "Memories added successfully"}
# Chat with memory
response = requests.post(
f"{BASE_URL}/chat",
headers=headers,
json={
"message": "What do I love?",
"user_id": "alice"
}
)
print(response.json()["response"])
# "Based on your memories, you love Python programming"
🚀 Ready for Production
The authentication layer is now production-ready for basic deployment with:
- ✅ Simple API key authentication
- ✅ User data isolation
- ✅ Proper error handling
- ✅ Environment-based configuration
- ✅ Comprehensive testing
For enterprise deployment, consider implementing the optional enhancements listed above.