Security Best Practices
Protecting user data is paramount. Follow these guidelines to build secure applications.
API Key Security
Never Expose Keys
// ❌ BAD - Key in client-side code
const client = new OfSelfClient({
apiKey: 'ofs_key_xxxxxxxxxxxxx' // Visible to users!
});
// ✅ GOOD - Call your backend instead
fetch('/api/nodes', { credentials: 'include' });
Use Environment Variables
# .env (never commit this file)
OFSELF_API_KEY=ofs_key_xxxxxxxxxxxxx
# .gitignore
.env
.env.local
*.env
import os
api_key = os.environ.get('OFSELF_API_KEY')
Rotate Keys Regularly
- Generate new API keys periodically
- Revoke old keys after migration
- Monitor key usage for anomalies
OAuth Security
Use HTTPS Everywhere
// ❌ BAD
redirect_uri: 'http://myapp.com/callback'
// ✅ GOOD
redirect_uri: 'https://myapp.com/callback'
Validate State Parameter
# When initiating OAuth
import secrets
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
# When receiving callback
if request.args.get('state') != session.get('oauth_state'):
return "Invalid state", 403
Store Tokens Securely
# ❌ BAD - Plain text storage
user.access_token = token
# ✅ GOOD - Encrypted storage
from cryptography.fernet import Fernet
cipher = Fernet(os.environ['ENCRYPTION_KEY'])
user.access_token = cipher.encrypt(token.encode())
Data Handling
Request Minimum Data
Only request the permissions you need:
{
"scope": {
"node_types": ["note"],
"permissions": ["read"]
}
}
Don't Store Raw Data
Process and discard when possible:
# ❌ BAD - Storing everything
db.save({
"user_id": user_id,
"all_nodes": nodes,
"raw_response": response
})
# ✅ GOOD - Store only what you need
processed_data = extract_relevant_fields(nodes)
db.save({"user_id": user_id, "summary": processed_data})
Handle Deletion Requests
Respect when users revoke access:
def on_share_revoked(user_id):
# Delete all cached user data
cache.delete_pattern(f"user:{user_id}:*")
db.execute("DELETE FROM user_cache WHERE user_id = ?", user_id)
Webhook Security
Verify Signatures
Always verify webhook authenticity:
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
computed = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(
f"sha256={computed}",
signature
)
Use HTTPS Endpoint
# ❌ BAD
https://api.ofself.ai/webhooks/subscribe
{
"url": "http://myapp.com/webhook" # Insecure!
}
# ✅ GOOD
{
"url": "https://myapp.com/webhook"
}
Input Validation
Validate User Input
from pydantic import BaseModel, validator
class CreateNodeRequest(BaseModel):
title: str
value: str
@validator('title')
def title_not_empty(cls, v):
if not v.strip():
raise ValueError('Title cannot be empty')
if len(v) > 500:
raise ValueError('Title too long')
return v.strip()
Sanitize Before Display
// ❌ BAD - XSS vulnerability
element.innerHTML = node.value;
// ✅ GOOD - Use text content or sanitize
element.textContent = node.value;
// or
element.innerHTML = DOMPurify.sanitize(node.value);
Error Handling
Don't Leak Sensitive Info
# ❌ BAD - Exposes internal details
except Exception as e:
return {"error": str(e), "stack": traceback.format_exc()}
# ✅ GOOD - Generic error message
except Exception as e:
logger.error(f"Error: {e}") # Log internally
return {"error": "An error occurred"}, 500
Log Securely
# ❌ BAD - Logging sensitive data
logger.info(f"User {user_id} authenticated with token {token}")
# ✅ GOOD - Redact sensitive values
logger.info(f"User {user_id} authenticated successfully")
Checklist
- API keys stored in environment variables
- API keys never in client-side code
- HTTPS for all endpoints
- OAuth state parameter validated
- Access tokens encrypted at rest
- Minimum necessary permissions requested
- Webhook signatures verified
- User input validated and sanitized
- Error messages don't leak internals
- Sensitive data not logged
- Data deleted when access revoked