Skip to main content

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