Security

Comprehensive security features for handling sensitive data, encrypted secrets, and token management in your forms.

🔒

Security First

Never store sensitive data like API tokens, passwords, or credentials in plain text. Use the encrypted secret context to protect your sensitive information.

Secret Context Architecture

The form builder provides a separate secretContext for storing encrypted sensitive data. This context works alongside the regular context but provides additional security through encryption.

How It Works

  1. 1
    Encrypt sensitive values using the provided encryption helpers
  2. 2
    Store encrypted values in secretContext (never in regular context)
  3. 3
    Reference secrets using {{secret:path}} placeholders
  4. 4
    Automatic decryption happens only when values are needed for API calls

Basic Usage

tsx
import { FormBuilder, encryptValue } from '@seanblock/form-builder';

// Generate or retrieve your encryption key
const encryptionKey = process.env.FORM_ENCRYPTION_KEY!;

// Encrypt sensitive values
const secretContext = {
  apiToken: encryptValue('your-api-token-here', encryptionKey),
  apiKey: encryptValue('your-api-key-here', encryptionKey),
};

// Regular context for non-sensitive data
const context = {
  userId: '123',
  userName: 'John Doe'
};

<FormBuilder
  config={formConfig}
  context={context}
  secretContext={secretContext}
  encryptionKey={encryptionKey}
  additionalBlocks={blocks}
/>

Using Secrets in Services

Reference encrypted secrets in your service configurations using {{secret:path}} placeholders:

json
{
  "services": {
    "fetchUserData": {
      "type": "rest",
      "url": "/api/user",
      "method": "GET",
      "bearer": "{{secret:apiToken}}"
    },
    "authenticateUser": {
      "type": "rest",
      "url": "/api/auth",
      "method": "POST",
      "auth": {
        "username": "{{secret:apiUsername}}",
        "password": "{{secret:apiPassword}}"
      }
    }
  }
}

Token Refresh Strategies

The form builder provides multiple strategies for managing token lifecycles, including automatic refresh.

1. Automatic Interval Refresh

Refresh tokens automatically at regular intervals (e.g., every 3 minutes):

json
{
  "lifecycle": {
    "onInterval": [
      {
        "interval": 180000,
        "actions": [
          {
            "type": "callApi",
            "service": "refreshToken",
            "then": [
              {
                "type": "assignSecret",
                "assign": {
                  "accessToken": "{{event:access_token}}"
                },
                "encrypt": true
              }
            ]
          }
        ]
      }
    ]
  }
}

2. On-Demand Refresh

Refresh tokens when form loads or when specific events occur:

json
{
  "lifecycle": {
    "onFormLoad": [
      {
        "type": "callApi",
        "service": "refreshToken",
        "then": [
          {
            "type": "assignSecret",
            "assign": {
              "accessToken": "{{event:access_token}}",
              "refreshToken": "{{event:refresh_token}}"
            },
            "encrypt": true
          },
          {
            "type": "assignContext",
            "assign": {
              "tokenExpiresAt": "{{event:expires_at}}"
            }
          }
        ]
      }
    ]
  }
}

3. Pre-emptive Refresh

Automatically refresh tokens before making API calls (coming soon):

json
{
  "services": {
    "getUserData": {
      "type": "rest",
      "url": "/api/user",
      "bearer": "{{secret:accessToken}}",
      "refreshBefore": {
        "service": "refreshToken"
      }
    }
  }
}

Encryption Key Management

✅ Best Practices

  • • Store keys in environment variables
  • • Use different keys for dev/prod
  • • Rotate keys periodically
  • • Use key derivation for per-form keys
  • • Never commit keys to git

❌ Avoid

  • • Hardcoding keys in source code
  • • Using weak or predictable keys
  • • Sharing keys across environments
  • • Storing keys in client-side code
  • • Using the same key forever

Environment Variables

bash
# .env.local
FORM_ENCRYPTION_KEY=your-secure-encryption-key-here
NEXT_PUBLIC_API_URL=https://api.example.com
tsx
// In your application
const encryptionKey = process.env.FORM_ENCRYPTION_KEY!;

if (!encryptionKey) {
  throw new Error('FORM_ENCRYPTION_KEY must be set');
}

Key Derivation

Generate form-specific keys from a master key:

tsx
import { deriveEncryptionKey } from '@seanblock/form-builder';

const masterKey = process.env.MASTER_ENCRYPTION_KEY!;
const formId = 'user-registration-form';

// Derive a unique key for this form
const encryptionKey = deriveEncryptionKey(masterKey, formId);

Security Best Practices

1. HTTPS Only in Production

The form builder enforces HTTPS in production when using secret context. This prevents man-in-the-middle attacks and ensures encrypted data is transmitted securely.

2. Separate Contexts

Keep sensitive data in secretContext and non-sensitive data in regular context:

tsx
// ❌ Bad: Token in regular context
const context = {
  apiToken: 'abc123',  // Plain text!
  userId: '123'
};

// ✅ Good: Token in secret context
const context = {
  userId: '123'  // Non-sensitive
};

const secretContext = {
  apiToken: encryptValue('abc123', key)  // Encrypted!
};

3. Secure Logging

The form builder automatically sanitizes logs to prevent token exposure. Sensitive values are redacted with •••••••• in development logs.

4. Memory Cleanup

Secrets are automatically cleared from memory when the form unmounts. This prevents sensitive data from lingering in browser memory.

Advanced Features

Audit Logging

Track security events with custom audit logging:

tsx
const auditLogger = {
  log: (event: string, data: any) => {
    console.log(`[AUDIT] ${event}`, {
      timestamp: Date.now(),
      ...data
    });
    
    // Send to your logging service
    sendToLoggingService({ event, data });
  }
};

<FormBuilder
  config={formConfig}
  context={context}
  secretContext={secretContext}
  encryptionKey={encryptionKey}
  auditLogging={auditLogger}
  additionalBlocks={blocks}
/>

Custom Encryption

Use your own encryption implementation:

tsx
import { encryptValue, decryptValue } from '@seanblock/form-builder';

// Encrypt values before passing to form
const myEncryptedValue = encryptValue('sensitive-data', encryptionKey);

// Or use your own encryption
const myCustomEncryption = yourEncryptFunction('sensitive-data');

Common Patterns

Multi-API Authentication

Handle multiple APIs with different tokens:

tsx
const secretContext = {
  userApiToken: encryptValue(userToken, key),
  paymentApiToken: encryptValue(paymentToken, key),
  analyticsApiKey: encryptValue(analyticsKey, key),
};
json
{
  "services": {
    "fetchUser": {
      "type": "rest",
      "url": "/api/user",
      "bearer": "{{secret:userApiToken}}"
    },
    "processPayment": {
      "type": "rest",
      "url": "/api/payment",
      "bearer": "{{secret:paymentApiToken}}"
    },
    "trackEvent": {
      "type": "rest",
      "url": "/api/analytics",
      "headers": {
        "X-API-Key": "{{secret:analyticsApiKey}}"
      }
    }
  }
}

OAuth Flow

Complete OAuth authentication with token refresh:

json
{
  "lifecycle": {
    "onFormLoad": [
      {
        "type": "callApi",
        "service": "exchangeCodeForToken",
        "then": [
          {
            "type": "assignSecret",
            "assign": {
              "accessToken": "{{event:access_token}}",
              "refreshToken": "{{event:refresh_token}}"
            },
            "encrypt": true
          }
        ]
      }
    ],
    "onInterval": [
      {
        "interval": 300000,
        "actions": [
          {
            "type": "callApi",
            "service": "refreshAccessToken",
            "then": [
              {
                "type": "assignSecret",
                "assign": {
                  "accessToken": "{{event:access_token}}"
                },
                "encrypt": true
              }
            ]
          }
        ]
      }
    ]
  }
}

Security Checklist

Before Going to Production

  • All sensitive data stored in secretContext, not context
  • Encryption keys stored in environment variables
  • HTTPS enabled for production
  • Token refresh configured (if applicable)
  • No sensitive data in service URLs
  • Audit logging configured (optional but recommended)
  • Test token refresh flow

Related Documentation