DAY 20 WEEK 4: INTEGRATION

Week 4 Review & Best Practices

Consolidate your integration knowledge, review security best practices, learn telemetry patterns, and complete the Week 4 assessment.

You've learned how to integrate plugins with every major EmpowerNow service: CRUD workflows, IdP tokens, PDP authorization, and BFF patterns. Today we consolidate that knowledge, add telemetry for production observability, and ensure you're ready to build enterprise-grade plugins.

๐Ÿ“‹Learning Objectives

  • Recap all Week 4 integration topics
  • Implement telemetry with sdk.telemetry
  • Apply security best practices
  • Complete the Week 4 assessment
  • Preview Week 5 advanced topics

๐Ÿ”„Week 4 Recap

Day 16 CRUD & Workflows
  • Workflow YAML definitions
  • Step types (connector, approval, notification)
  • sdk.crud.run() to trigger workflows
  • SSE subscription for progress
  • CrudGrid component
Day 17 IdP Integration
  • Token flow architecture
  • User context via /api/idp/userinfo
  • Token exchange (RFC 8693)
  • On-Behalf-Of (OBO) flow
  • DPoP proof-of-possession
Day 18 PDP Integration
  • AuthZEN evaluation API
  • sdk.authz.evaluate()
  • Batch evaluation for performance
  • IfCan component pattern
  • Decision context & explanations
Day 19 BFF & API Patterns
  • Proxy routing configuration
  • Session management (cookies + Redis)
  • CSRF protection
  • Error handling patterns
  • LLM proxy for AI features

๐Ÿ“ŠTelemetry & Observability

Production plugins need observability. The SDK provides sdk.telemetry for structured logging, metrics, and tracing.

Using sdk.telemetry

// src/utils/telemetry.ts
const sdk = (globalThis as any).pluginSdk;

// Structured logging
export function logInfo(message: string, data?: Record<string, any>) {
  sdk.telemetry.log('info', message, {
    plugin: 'role-viewer',
    ...data
  });
}

export function logError(message: string, error: Error, data?: Record<string, any>) {
  sdk.telemetry.log('error', message, {
    plugin: 'role-viewer',
    error: error.message,
    stack: error.stack,
    ...data
  });
}

// Track user actions
export function trackEvent(event: string, properties?: Record<string, any>) {
  sdk.telemetry.track(event, {
    plugin: 'role-viewer',
    timestamp: new Date().toISOString(),
    ...properties
  });
}

// Measure performance
export function measureTiming(name: string, durationMs: number) {
  sdk.telemetry.timing(name, durationMs, {
    plugin: 'role-viewer'
  });
}

Usage in Components

// Track API call timing
const fetchRoles = async () => {
  const startTime = performance.now();
  
  try {
    logInfo('Fetching user roles');
    
    const response = await sdk.api.fetch('/api/membership/users/me/roles');
    const data = await response.json();
    
    const duration = performance.now() - startTime;
    measureTiming('fetch_roles', duration);
    
    trackEvent('roles_loaded', { 
      count: data.roles?.length || 0,
      duration_ms: Math.round(duration)
    });
    
    return data;
  } catch (err: any) {
    logError('Failed to fetch roles', err);
    trackEvent('roles_load_error', { error: err.message });
    throw err;
  }
};

// Track user interactions
const handleRoleClick = (roleId: string) => {
  trackEvent('role_clicked', { role_id: roleId });
  navigateToDetail(roleId);
};
๐Ÿ’ก Telemetry Best Practices
  • Always include plugin name for filtering
  • Use structured data, not string concatenation
  • Track both success and failure paths
  • Measure timing for performance-critical operations
  • Don't log sensitive data (PII, tokens, passwords)

๐Ÿ”’Security Best Practices

1. Always Use sdk.api.fetch()

Never use raw fetch() for API calls. The SDK handles CSRF, session cookies, and error normalization.

// โŒ Wrong - missing CSRF, session cookie issues
const response = await fetch('/api/crud/workflows/run', {
  method: 'POST',
  body: JSON.stringify(data)
});

// โœ… Correct - SDK handles security automatically
const response = await sdk.api.fetch('/api/crud/workflows/run', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(data)
});

2. Validate All User Input

// Use zod for runtime validation
import { z } from 'zod';

const AccessRequestSchema = z.object({
  resourceId: z.string().uuid(),
  justification: z.string().min(20).max(1000),
  duration: z.enum(['24h', '7d', '30d', 'permanent'])
});

const handleSubmit = (formData: unknown) => {
  // Throws if invalid
  const validated = AccessRequestSchema.parse(formData);
  
  // Safe to use
  return sdk.crud.run('access-request', validated);
};

3. Fail Closed on Auth Errors

// Authorization check with fail-closed pattern
const canPerformAction = async (resource: string, action: string): Promise<boolean> => {
  try {
    const result = await sdk.authz.evaluate({
      resource: { type: resource },
      action: { name: action }
    });
    return result.decision === true;
  } catch (err) {
    logError('Auth check failed', err as Error);
    // Fail closed - deny on error
    return false;
  }
};

4. Sanitize User-Generated Content

// Never render untrusted HTML directly

// โŒ XSS vulnerability
<div dangerouslySetInnerHTML={{ __html: userComment }} />

// โœ… Use text content (auto-escaped)
<div>{userComment}</div>

// โœ… Or sanitize if HTML is required
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userComment) }} />

5. Protect Secrets

// โŒ Never hardcode secrets
const API_KEY = 'sk-abc123secret';

// โŒ Never log tokens/secrets
console.log('Token:', accessToken);

// โœ… Secrets should only be in backend environment variables
// Plugins get tokens through secure httpOnly cookies managed by BFF

Security Checklist

Check Why It Matters
โœ… Use sdk.api.fetch() Automatic CSRF, session, error handling
โœ… Validate all input Prevent injection and data corruption
โœ… Check permissions before actions Defense in depth with PDP
โœ… Fail closed on auth errors Deny access when uncertain
โœ… Don't log sensitive data Prevent credential leakage
โœ… Sanitize user content Prevent XSS attacks
โœ… Use HTTPS only Encrypt data in transit

๐Ÿš€Production Deployment Checklist

  • Build: Production bundle passes without errors
  • Integrity: SHA-256 hash updated in plugins.yaml
  • Permissions: All required API endpoints in permissions.api
  • PDP Application: pdp_application matches policy configuration
  • Telemetry: Logging and event tracking implemented
  • Error handling: All API calls have proper error handlers
  • Loading states: Skeleton UI for async operations
  • Empty states: Meaningful UI when no data
  • Authorization: All sensitive actions gated with PDP checks
  • Documentation: README with setup and usage instructions

โ“Knowledge Check

1 How do you trigger a workflow from a plugin? โ–ผ
const result = await sdk.crud.run('workflow-name', {
  input_field: 'value'
});
console.log(result.workflow_run_id);
2 What's the difference between Token Exchange and OBO? โ–ผ

Token Exchange: Trade your token for one with different audience/scopes. Used when you need a different token format.

OBO (On-Behalf-Of): A service acts as the user when calling another service. Preserves user identity in the call chain. The act claim tracks the acting party.

3 How do you check 10 permissions efficiently? โ–ผ

Use batch evaluation with sdk.authz.batchEvaluate():

const results = await sdk.authz.batchEvaluate([
  { resource: { type: 'doc', id: '1' }, action: { name: 'edit' } },
  { resource: { type: 'doc', id: '2' }, action: { name: 'edit' } },
  // ... more
]);
// 1 API call instead of 10!
4 Why can't plugins access tokens directly? โ–ผ

Tokens are stored in httpOnly cookies, which JavaScript cannot access. This protects against XSS attacksโ€”even if malicious script runs, it can't steal the token. The BFF retrieves tokens from Redis using the session ID.

5 What happens if you call an endpoint not in plugins.yaml permissions.api? โ–ผ

The BFF returns 403 Forbidden. The plugin allowlist acts as a security boundaryโ€”plugins can only call explicitly permitted endpoints. Always add new endpoints to permissions.api in plugins.yaml before using them.

๐Ÿ†Week 4 Assessment

Before moving to Week 5, verify you can complete these tasks:

โœ… Trigger Workflow

Use sdk.crud.run() with workflow name and input

โœ… Monitor Progress

Subscribe to workflow SSE events

โœ… Get User Info

Fetch /api/idp/userinfo for user context

โœ… Check Permission

Use sdk.authz.evaluate() with fail-closed

โœ… Batch Permissions

Use sdk.authz.batchEvaluate() for lists

โœ… Handle Errors

Status-specific handling for 401, 403, 429

๐ŸŽ‰ Week 4 Complete!

You've mastered integration patterns for workflows, identity, authorization, and the BFF. Your plugins can now orchestrate business processes, enforce fine-grained permissions, and provide real-time feedback.

๐Ÿ”ฎWeek 5 Preview: Advanced Topics

Week 5 covers advanced platform capabilities:

Day Topic What You'll Learn
21 MCP Integration Model Context Protocol for AI tool integration
22 LLM Proxy & AI Multi-provider AI, budget controls, cost tracking
23 ARIA Framework AI Agent authorization, delegations, chain validation
24 NowConnect Hybrid connectivity for on-premises systems
25 Receipts & Audit Tamper-evident audit trails, compliance
๐Ÿค– AI-Ready Plugins

Week 5 teaches you how to build plugins that leverage AI capabilities while maintaining security and cost control. Learn how to authorize AI agents, track LLM costs, and create audit trails for AI-assisted operations.

๐Ÿ“Day 20 Checkpoint

  • Reviewed all Week 4 integration topics
  • Implemented telemetry with sdk.telemetry
  • Applied security best practices
  • Completed Week 4 assessment
  • Ready for Week 5 advanced topics