Security Best Practices
Ensure your Connect UI implementation follows security best practices to protect your users and their data.
API Key Management
Never Expose API Keys Client-Side
❌ Wrong: API key in frontend code
// DON'T DO THIS
const response = await fetch('https://app.unizo.ai/api/v1/serviceKeys', {
headers: {
'apikey': 'sk_live_abcd1234' // Exposed API key!
}
});
✅ Correct: API key on server only
// Backend API endpoint
app.post('/api/generate-service-key', async (req, res) => {
const response = await fetch('https://app.unizo.ai/api/v1/serviceKeys', {
headers: {
'apikey': process.env.UNIZO_API_KEY // Secure server-side
}
});
res.json({ serviceKey: response.serviceKey });
});
// Frontend calls your backend
const { serviceKey } = await fetch('/api/generate-service-key').then(r => r.json());
Service Key Security
Generate Fresh Keys
Always generate new service keys for each authentication session:
// Good: New key per session
async function startAuthFlow() {
const { serviceKey } = await generateServiceKey();
// Use immediately
}
// Bad: Reusing old keys
const STATIC_KEY = '196fe56aaae7c00a284dd96452b'; // Don't do this!
Set Appropriate Expiration
Configure service keys with reasonable expiration times:
{
"expirationMinutes": 30, // Expire after 30 minutes
"singleUse": true // Invalidate after first use
}
Iframe Security
Content Security Policy
Configure CSP headers to allow Unizo's domain:
Content-Security-Policy: frame-src https://dock.unizo.ai;
Sandbox Attributes
Use appropriate sandbox attributes:
<iframe
src="https://dock.unizo.ai/links/YOUR_KEY"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
/>
Message Validation
Verify Origin
Always validate the origin of postMessage events:
window.addEventListener('message', (event) => {
// Verify the message is from Unizo
if (event.origin !== 'https://dock.unizo.ai') {
console.warn('Ignoring message from unknown origin:', event.origin);
return;
}
// Safe to process
handleAuthMessage(event.data);
});
Validate Message Structure
Check message format before processing:
function handleAuthMessage(data) {
// Validate expected structure
if (!data.type || !data.integrationId) {
console.error('Invalid message format');
return;
}
switch (data.type) {
case 'AUTH_SUCCESS':
handleSuccess(data);
break;
case 'AUTH_ERROR':
handleError(data);
break;
}
}
Data Protection
Minimal Data Exposure
Only include necessary information in requests:
{
"subOrganization": {
"externalKey": "cust_123", // Use IDs, not sensitive data
"name": "Acme Inc" // Public name only
}
}
Secure Storage
Never store credentials or tokens client-side:
// Bad: Storing in localStorage
localStorage.setItem('userToken', token);
// Good: Let backend handle storage
await fetch('/api/store-integration', {
method: 'POST',
body: JSON.stringify({ integrationId })
});
Network Security
HTTPS Only
Always use HTTPS in production:
// Enforce HTTPS
if (location.protocol !== 'https:' && !location.hostname.includes('localhost')) {
location.protocol = 'https:';
}
Certificate Pinning
For mobile apps, implement certificate pinning for API calls.
Monitoring & Compliance
Audit Logs
Track authentication attempts:
async function logAuthAttempt(userId, provider, success) {
await fetch('/api/audit-log', {
method: 'POST',
body: JSON.stringify({
userId,
provider,
success,
timestamp: new Date().toISOString(),
ip: req.ip
})
});
}
Rate Limiting
Implement rate limiting for service key generation:
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10 // Limit to 10 requests per window
});
app.post('/api/generate-service-key', rateLimiter, generateKey);
Security Checklist
- API keys stored securely server-side
- Service keys generated per session
- CSP headers configured
- PostMessage origin validation
- HTTPS enforced
- Rate limiting implemented
- Audit logging enabled
- Error messages don't expose sensitive info
- Regular security audits scheduled