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:

  1. User logs in with email/password → session token created
  2. Session token stored in localStorage and attached to all API requests
  3. Sessions stored in PostgreSQL with expiration
  4. 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

RoleDescription
super_adminFull system access including user management and key rotation
adminManage all entities, users, but limited key access
editorCreate and modify topics, schemas, mappings, webhooks
viewerRead-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

EntityApp-ScopedEnv-Scoped
TopicsYesYes
SchemasYesYes
MappingsYesYes
WebhooksYesYes
Webhook LogsYesYes
DB SubscriptionsYesYes
DeploymentsYesCross-env
Sync/DocumentsVia queryVia query
Socket/DebuggerNo (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_SECRET or 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