Security
Security
JWT authentication, signing key rotation, RBAC, MFA, webhook signing, and multi-tenant isolation.
The Realtime Platform provides a comprehensive security model covering authentication, authorization, data isolation, and webhook integrity.
Authentication
JWT Tokens
Service-to-service and SDK connections authenticate via JWT tokens:
- Tokens are signed with HMAC-SHA256 using managed signing keys
- Each token includes a
kid(key ID) header for key rotation support - Tokens carry permission claims (
admin,service,client)
// Issue a token
POST /auth/token
{ "applicationId": "app_abc", "permissions": ["subscribe"] }
// Verify a token
POST /auth/verify
{ "token": "eyJhbGci..." }Session-Based Auth (Admin UI)
The Admin UI uses session-based authentication:
- User logs in with email/password → session token created
- Session token stored in
localStorageand attached to all API requests - Sessions stored in PostgreSQL with expiration
- Logout invalidates the session server-side
Multi-Factor Authentication (MFA)
TOTP-based MFA compatible with Google Authenticator, Authy, and 1Password:
- Admin can enable MFA per user
- QR code setup flow with TOTP verification
- MFA challenge on login when enabled
- Admin can disable MFA for any user
Signing Key Management
JWT signing keys support rotation without downtime:
Create a new key
Generate a new signing key via the Admin API or UI. Both old and new keys are active.
New tokens use the new key
All newly issued tokens are signed with the latest active key.
Deactivate the old key
After existing tokens expire, deactivate the old key. It can no longer sign new tokens, but tokens already in circulation remain valid until their exp claim.
The Rotate operation combines steps 1 and 3 atomically — creates a new key and deactivates the old one in a single call.
Key Storage
Keys are stored in PostgreSQL (signing_keys table) and loaded into memory on startup. The PgSigningKeyStore provides async CRUD while the in-memory SigningKeyStore provides sync access for hot-path JWT operations.
Role-Based Access Control (RBAC)
Built-in Roles
| Role | Description |
|---|---|
super_admin | Full system access including user management and key rotation |
admin | Manage all entities, users, but limited key access |
editor | Create and modify topics, schemas, mappings, webhooks |
viewer | Read-only access to all entities |
Permission Model
Permissions are defined as (resource, action) pairs:
Resources: topics, schemas, mappings, webhooks, users, roles, invites, keys, deployments, debugger, documents, socket
Actions: read, create, update, delete
Custom Roles
Create custom roles with specific permission combinations via the Admin UI or API:
{
"name": "webhook_manager",
"permissions": [
{ "resource": "webhooks", "action": "read" },
{ "resource": "webhooks", "action": "create" },
{ "resource": "webhooks", "action": "update" },
{ "resource": "webhooks", "action": "delete" }
]
}Middleware
The requirePermission middleware checks permissions on protected routes:
router.post('/api/topics', requirePermission('topics', 'create'), handler);Multi-Tenant Isolation
Application Scoping
Every API request carries an X-Application-Id header:
- Backend middleware extracts it into
req.applicationId - All entity-owning APIs filter lists and tag creates by application
- The Admin UI auto-attaches the header from the currently selected application
Scoped Entities
| Entity | App-Scoped | Env-Scoped |
|---|---|---|
| Topics | Yes | Yes |
| Schemas | Yes | Yes |
| Mappings | Yes | Yes |
| Webhooks | Yes | Yes |
| Webhook Logs | Yes | Yes |
| DB Subscriptions | Yes | Yes |
| Deployments | Yes | Cross-env |
| Sync/Documents | Via query | Via query |
| Socket/Debugger | No (operational) | No |
Environment Scoping
Data is further isolated by environment (development, staging, production). The X-Environment header controls which environment's data is accessed.
Webhook Signing
Every webhook delivery is signed with HMAC-SHA256:
X-Webhook-Signature: <hex-encoded HMAC-SHA256>Verification on the receiving end:
import crypto from 'crypto';
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Warning
Always use timingSafeEqual for signature comparison to prevent timing attacks.
Password Security
- Passwords are hashed with bcrypt (cost factor 10)
- Password reset tokens are single-use with expiration
- The setup endpoint is disabled after the first admin account is created
Production Security Checklist
- Set strong, unique
JWT_SECRETor use managed signing keys - Enable TLS for all database and Redis connections
- Set
NODE_ENV=production - Configure CORS to restrict allowed origins
- Enable MFA for all admin accounts
- Rotate signing keys periodically
- Use separate database credentials for the platform DB and CDC target DB
- Review role assignments — apply principle of least privilege
- Monitor webhook delivery logs for unauthorized access attempts