# Salesforce-to-Portal Security Integration Guide ## Overview This guide outlines secure patterns for **Salesforce-to-Portal communication** specifically for the **order provisioning workflow**. Based on your architecture, this focuses on order status updates, not invoice handling. ## Order Provisioning Flow ``` Portal Customer → Places Order → Salesforce Order (Pending Review) ↓ Salesforce Operator → Reviews → Clicks "Provision in WHMCS" Quick Action ↓ Salesforce → Calls Portal BFF → `/orders/{sfOrderId}/fulfill` ↓ Portal BFF → Provisions in WHMCS → Updates Salesforce Order Status ↓ Portal → Polls Order Status → Shows Customer Updates ``` ## 1. Secure Order Provisioning Communication ### Primary Method: Direct HTTPS Webhook (Recommended for Order Flow) Based on your architecture, the **order provisioning flow** uses direct HTTPS calls from Salesforce to your portal BFF. Here's how to secure this: **Salesforce → Portal BFF Flow:** 1. **Salesforce Quick Action** calls `POST /orders/{sfOrderId}/fulfill` 2. **Portal BFF** processes the provisioning request 3. **Optional: Portal → Salesforce** status updates via webhook ### Secure Salesforce Quick Action Setup **In Salesforce:** 1. **Named Credential Configuration** ```xml Portal_BFF https://your-portal-api.com Anonymous HttpsOnly false ``` 2. **Apex Class for Secure Webhook Calls** ```apex public class PortalWebhookService { private static final String WEBHOOK_SECRET = '{!$Credential.Portal_Webhook.Password}'; @future(callout=true) public static void provisionOrder(String orderId) { try { // Prepare secure payload Map payload = new Map{ 'orderId' => orderId, 'timestamp' => System.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''), 'nonce' => generateNonce() }; // Create HMAC signature String jsonPayload = JSON.serialize(payload); String signature = generateHMACSignature(jsonPayload, WEBHOOK_SECRET); // Make secure HTTP call HttpRequest req = new HttpRequest(); req.setEndpoint('callout:Portal_BFF/orders/' + orderId + '/fulfill'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setHeader('X-SF-Signature', signature); req.setHeader('X-SF-Timestamp', payload.get('timestamp').toString()); req.setHeader('X-SF-Nonce', payload.get('nonce').toString()); req.setHeader('Idempotency-Key', generateIdempotencyKey(orderId)); req.setBody(jsonPayload); req.setTimeout(30000); // 30 second timeout Http http = new Http(); HttpResponse res = http.send(req); // Handle response handleProvisioningResponse(orderId, res); } catch (Exception e) { // Log error and update order status System.debug('Provisioning failed for order ' + orderId + ': ' + e.getMessage()); updateOrderProvisioningStatus(orderId, 'Failed', e.getMessage()); } } private static String generateHMACSignature(String data, String key) { Blob hmacData = Crypto.generateMac('HmacSHA256', Blob.valueOf(data), Blob.valueOf(key)); return EncodingUtil.convertToHex(hmacData); } private static String generateNonce() { return EncodingUtil.convertToHex(Crypto.generateAesKey(128)).substring(0, 16); } private static String generateIdempotencyKey(String orderId) { return 'provision_' + orderId + '_' + System.now().getTime(); } } ### Optional: Portal → Salesforce Status Updates If you want the portal to send status updates back to Salesforce (e.g., when provisioning completes), you can set up a reverse webhook: **Portal BFF → Salesforce Webhook Endpoint:** ```typescript // In your Portal BFF export class SalesforceStatusUpdateService { async updateOrderStatus(orderId: string, status: string, details?: any) { const payload = { orderId, status, timestamp: new Date().toISOString(), details: this.sanitizeDetails(details) }; // Send to Salesforce webhook endpoint await this.sendToSalesforce('/webhook/order-status', payload); } } ``` ## 2. Portal BFF Security Implementation ### Enhanced Order Provisioning Endpoint Your portal BFF should implement the `/orders/{sfOrderId}/fulfill` endpoint with these security measures: ```typescript // Enhanced order fulfillment endpoint @Post('orders/:sfOrderId/fulfill') @UseGuards(EnhancedWebhookSignatureGuard) async fulfillOrder( @Param('sfOrderId') sfOrderId: string, @Body() payload: ProvisionOrderRequest, @Headers('idempotency-key') idempotencyKey: string ) { // Your existing fulfillment logic return await this.orderFulfillmentService.fulfillOrder(sfOrderId, payload, idempotencyKey); } ``` **Enhanced Webhook Security Implementation:** ```typescript @Injectable() export class EnhancedWebhookSignatureGuard implements CanActivate { constructor(private configService: ConfigService) {} canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); // 1. Verify HMAC signature (existing) this.verifyHmacSignature(request); // 2. Verify timestamp (prevent replay attacks) this.verifyTimestamp(request); // 3. Verify nonce (prevent duplicate processing) this.verifyNonce(request); // 4. Verify source IP (if using IP allowlisting) this.verifySourceIp(request); return true; } private verifyTimestamp(request: Request): void { const timestamp = request.headers['x-sf-timestamp'] as string; if (!timestamp) { throw new UnauthorizedException('Timestamp required'); } const requestTime = new Date(timestamp).getTime(); const now = Date.now(); const maxAge = 5 * 60 * 1000; // 5 minutes if (Math.abs(now - requestTime) > maxAge) { throw new UnauthorizedException('Request too old'); } } private verifyNonce(request: Request): void { const nonce = request.headers['x-sf-nonce'] as string; if (!nonce) { throw new UnauthorizedException('Nonce required'); } // Check if nonce was already used (implement nonce store) // This prevents replay attacks } } ``` ## 2. Outbound Security: Portal → Salesforce ### Current Implementation (Already Secure ✅) Your existing JWT-based authentication is excellent: ```typescript // Your current pattern in salesforce-connection.service.ts // Uses private key JWT authentication - industry standard ``` ### Enhanced Patterns for Sensitive Operations For highly sensitive operations, consider adding: ```typescript @Injectable() export class SecureSalesforceService { async createSensitiveRecord(data: SensitiveData, idempotencyKey: string) { // 1. Encrypt sensitive fields before sending const encryptedData = this.encryptSensitiveFields(data); // 2. Add idempotency protection const headers = { 'Idempotency-Key': idempotencyKey, 'X-Request-ID': uuidv4(), }; // 3. Use your existing secure connection return await this.salesforceConnection.create(encryptedData, headers); } private encryptSensitiveFields(data: any): any { // Encrypt PII fields before transmission const sensitiveFields = ['ssn', 'creditCard', 'personalId']; // Implementation depends on your encryption strategy } } ``` ## 3. Data Protection Guidelines ### Sensitive Data Handling ```typescript // Example: Secure order processing export class SecureOrderService { async processOrderApproval(orderData: OrderApprovalData) { // 1. Validate customer permissions await this.validateCustomerAccess(orderData.customerNumber); // 2. Sanitize data for logging const sanitizedData = this.sanitizeForLogging(orderData); this.logger.log('Processing order approval', sanitizedData); // 3. Process with minimal data exposure const result = await this.processOrder(orderData); // 4. Audit trail without sensitive data await this.createAuditLog({ action: 'order_approved', customerNumber: orderData.customerNumber, orderId: orderData.orderId, timestamp: new Date(), // No sensitive payment or personal data }); return result; } private sanitizeForLogging(data: any): any { // Remove or mask sensitive fields for logging const { creditCard, ssn, ...safeData } = data; return { ...safeData, creditCard: creditCard ? '****' + creditCard.slice(-4) : undefined, ssn: ssn ? '***-**-' + ssn.slice(-4) : undefined, }; } } ``` ### Field-Level Security ```typescript // Implement field-level encryption for highly sensitive data export class FieldEncryptionService { private readonly algorithm = 'aes-256-gcm'; private readonly keyDerivation = 'pbkdf2'; async encryptField(value: string, fieldType: string): Promise { const key = await this.deriveKey(fieldType); const iv = crypto.randomBytes(16); const cipher = crypto.createCipher(this.algorithm, key); let encrypted = cipher.update(value, 'utf8', 'hex'); encrypted += cipher.final('hex'); return { value: encrypted, iv: iv.toString('hex'), tag: cipher.getAuthTag().toString('hex'), }; } async decryptField(encryptedField: EncryptedField, fieldType: string): Promise { const key = await this.deriveKey(fieldType); const decipher = crypto.createDecipher(this.algorithm, key); decipher.setAuthTag(Buffer.from(encryptedField.tag, 'hex')); let decrypted = decipher.update(encryptedField.value, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } } ``` ## 4. Implementation Checklist ### Salesforce Setup - [ ] Create Platform Events for portal notifications - [ ] Set up Named Credentials for portal webhook calls - [ ] Configure IP allowlisting for portal endpoints - [ ] Implement HMAC signing in Apex - [ ] Create audit trails for all portal communications ### Portal Setup - [ ] Enhance webhook signature verification - [ ] Implement timestamp and nonce validation - [ ] Add IP allowlisting for Salesforce - [ ] Create encrypted payload handling - [ ] Implement idempotency protection ### Security Measures - [ ] Rotate webhook secrets regularly - [ ] Monitor for suspicious webhook activity - [ ] Implement rate limiting per customer - [ ] Add comprehensive audit logging - [ ] Test disaster recovery procedures ## 5. Monitoring and Alerting ```typescript @Injectable() export class SecurityMonitoringService { async monitorWebhookSecurity(request: Request, response: any) { const metrics = { sourceIp: request.ip, userAgent: request.headers['user-agent'], timestamp: new Date(), success: response.success, processingTime: response.processingTime, }; // Alert on suspicious patterns if (this.detectSuspiciousActivity(metrics)) { await this.sendSecurityAlert(metrics); } // Log for audit this.logger.log('Webhook security metrics', metrics); } private detectSuspiciousActivity(metrics: any): boolean { // Implement your security detection logic // - Too many requests from same IP // - Unusual timing patterns // - Failed authentication attempts return false; } } ``` ## 6. Testing Security ```typescript describe('Webhook Security', () => { it('should reject webhooks without valid HMAC signature', async () => { const invalidPayload = { data: 'test' }; const response = await request(app) .post('/webhooks/salesforce') .send(invalidPayload) .expect(401); expect(response.body.message).toContain('Invalid webhook signature'); }); it('should reject old timestamps', async () => { const oldTimestamp = new Date(Date.now() - 10 * 60 * 1000); // 10 minutes ago const payload = { data: 'test' }; const signature = generateHmacSignature(payload); const response = await request(app) .post('/webhooks/salesforce') .set('X-SF-Signature', signature) .set('X-SF-Timestamp', oldTimestamp.toISOString()) .send(payload) .expect(401); }); }); ``` ## 7. Production Deployment ### Environment Variables ```bash # Webhook Security SF_WEBHOOK_SECRET=your_256_bit_secret_key_here SF_WEBHOOK_IP_ALLOWLIST=13.108.0.0/14,204.14.232.0/23 WEBHOOK_TIMESTAMP_TOLERANCE=300000 # 5 minutes in ms # Encryption FIELD_ENCRYPTION_KEY=your_field_encryption_master_key ENCRYPTION_KEY_ROTATION_DAYS=90 # Monitoring SECURITY_ALERT_WEBHOOK=https://your-monitoring-service.com/alerts AUDIT_LOG_RETENTION_DAYS=2555 # 7 years for compliance ``` ### Salesforce Named Credential Setup ```xml Portal_Webhook https://your-portal-api.com Anonymous HttpsOnly false ``` This guide provides a comprehensive, production-ready approach to secure Salesforce-Portal integration that builds on your existing security infrastructure while adding enterprise-grade protection for sensitive data transmission.