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
- 1Encrypt sensitive values using the provided encryption helpers
- 2Store encrypted values in secretContext (never in regular context)
- 3Reference secrets using
{{secret:path}}placeholders - 4Automatic decryption happens only when values are needed for API calls
Basic Usage
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:
{
"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):
{
"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:
{
"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):
{
"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
# .env.local
FORM_ENCRYPTION_KEY=your-secure-encryption-key-here
NEXT_PUBLIC_API_URL=https://api.example.com// 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:
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:
// ❌ 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:
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:
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:
const secretContext = {
userApiToken: encryptValue(userToken, key),
paymentApiToken: encryptValue(paymentToken, key),
analyticsApiKey: encryptValue(analyticsKey, key),
};{
"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:
{
"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