diff --git a/SUMMARY.md b/SUMMARY.md index 80a1ac9..f1f7663 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -31,9 +31,17 @@ * [V2 to V3 Migration Guide](guides/migration-v2-to-v3.md) * [Workflow Dispatcher Architecture](guides/architecture/workflow-dispatcher.md) +* [Onboarding](guides/onboarding/README.md) + * [Hosting Elsa in an Existing App](guides/onboarding/hosting-elsa-in-existing-app.md) * [Authentication & Authorization](guides/authentication.md) * [Security & Authentication](guides/security/README.md) + * [Disable Auth in Development](guides/security/disable-auth.md) + * [External Identity Providers](guides/security/external-identity-providers.md) +* [Deployment](guides/deployment/README.md) + * [Kubernetes Basics](guides/deployment/kubernetes.md) * [Kubernetes Deployment](guides/kubernetes-deployment.md) +* [Integration](guides/integration/README.md) + * [Blazor Dashboard](guides/integration/blazor-dashboard.md) * [Clustering](guides/clustering/README.md) * [Performance & Scaling](guides/performance/README.md) * [Throughput Tuning](guides/performance/examples/throughput-tuning.md) diff --git a/guides/deployment/README.md b/guides/deployment/README.md new file mode 100644 index 0000000..32dea0b --- /dev/null +++ b/guides/deployment/README.md @@ -0,0 +1,14 @@ +# Deployment + +This section covers deploying Elsa Workflows to various environments and platforms. + +## Guides in This Section + +* [Kubernetes Basics](kubernetes.md) - Quick-start guide for deploying Elsa to Kubernetes with PostgreSQL persistence, including troubleshooting and production best practices. + +## Related Documentation + +- [Kubernetes Deployment (Full Guide)](../kubernetes-deployment.md) - Comprehensive Kubernetes deployment guide with Helm charts, autoscaling, and monitoring +- [Clustering](../clustering/README.md) - Multi-node deployment patterns +- [Security & Authentication](../security/README.md) - Securing your deployments +- [Database Configuration](../../getting-started/database-configuration.md) - Persistence setup diff --git a/guides/deployment/kubernetes.md b/guides/deployment/kubernetes.md new file mode 100644 index 0000000..85bee26 --- /dev/null +++ b/guides/deployment/kubernetes.md @@ -0,0 +1,919 @@ +--- +description: >- + Quick start guide for deploying Elsa Workflows to Kubernetes with PostgreSQL persistence, including configuration, troubleshooting, and production best practices. +--- + +# Kubernetes Deployment Basics + +This guide provides a practical introduction to deploying Elsa Workflows on Kubernetes with PostgreSQL persistence. It focuses on common deployment patterns and configuration challenges, helping you move from SQLite (development) to PostgreSQL (production). + +{% hint style="info" %} +**Note:** For comprehensive Kubernetes deployment documentation including Helm charts, autoscaling, monitoring, and service mesh integration, see the [Full Kubernetes Deployment Guide](../kubernetes-deployment.md). +{% endhint %} + +## Overview + +This guide covers: + +- Using Kubernetes manifests from the elsa-core repository +- Switching from SQLite to PostgreSQL for production +- Common configuration pitfalls and how to avoid them +- Troubleshooting database connectivity and persistence + +## Prerequisites + +- Kubernetes cluster (v1.24+) - Minikube, k3s, or cloud provider (EKS, AKS, GKE) +- `kubectl` CLI configured to access your cluster +- Basic understanding of Kubernetes concepts (Pods, Services, ConfigMaps, Secrets) +- PostgreSQL database (managed or self-hosted) + +## Understanding the elsa-core Kubernetes Manifests + +The elsa-core repository includes sample Kubernetes manifests in the `scripts/` directory (if available). These manifests provide a starting point for deploying Elsa Server and Studio to Kubernetes. + +### Typical Manifest Structure + +``` +elsa-core/ +├── scripts/ +│ ├── kubernetes/ +│ │ ├── elsa-server-deployment.yaml +│ │ ├── elsa-server-service.yaml +│ │ ├── elsa-studio-deployment.yaml +│ │ ├── configmap.yaml +│ │ └── secrets.yaml (template) +``` + +{% hint style="warning" %} +**Note:** The exact structure may vary by version. Always refer to the latest elsa-core repository for current manifest examples. If manifests are not present, use the examples in this guide as a starting point. +{% endhint %} + +### What the Manifests Provide + +- **elsa-server-deployment.yaml**: Deployment for Elsa Server (workflow runtime and API) +- **elsa-studio-deployment.yaml**: Deployment for Elsa Studio (designer UI) +- **services.yaml**: ClusterIP or LoadBalancer services for external access +- **configmap.yaml**: Application configuration (connection strings, feature flags) +- **secrets.yaml**: Sensitive data (database passwords, API keys) + +## Default Configuration: SQLite + +By default, Elsa Server deployments often use SQLite for simplicity: + +**Default ConfigMap:** +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: elsa-config +data: + ConnectionStrings__DefaultConnection: "Data Source=/app/data/elsa.db" + Elsa__Persistence__Provider: "Sqlite" +``` + +**Why SQLite is Used:** +- Zero configuration required +- Works out of the box +- Suitable for demos and development + +**Why SQLite is NOT Suitable for Production:** +- **Single-file database**: Does not support multiple pods (no horizontal scaling) +- **No concurrent writes**: Workflow execution errors under load +- **Data loss risk**: Data is lost if the pod restarts (unless using PersistentVolume) +- **Limited performance**: Not optimized for high-throughput scenarios + +{% hint style="danger" %} +**Never use SQLite in production Kubernetes deployments.** Always use PostgreSQL, SQL Server, or MySQL for production workloads. +{% endhint %} + +## Switching to PostgreSQL + +To use PostgreSQL in Kubernetes, you need to: + +1. Deploy or connect to a PostgreSQL database +2. Update connection strings and environment variables +3. Configure Elsa modules to use the PostgreSQL provider +4. Apply database migrations + +### Step 1: Deploy PostgreSQL (Optional) + +If you don't have an external PostgreSQL instance, deploy one in Kubernetes: + +**postgres-deployment.yaml:** +```yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:16 + env: + - name: POSTGRES_DB + value: elsa_workflows + - name: POSTGRES_USER + value: elsa_user + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: password + ports: + - containerPort: 5432 + volumeMounts: + - name: postgres-storage + mountPath: /var/lib/postgresql/data + volumes: + - name: postgres-storage + persistentVolumeClaim: + claimName: postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 + type: ClusterIP +``` + +**postgres-secret.yaml:** +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secret +type: Opaque +stringData: + password: "your-secure-password-here" # Change this! +``` + +Apply the manifests: +```bash +kubectl apply -f postgres-secret.yaml +kubectl apply -f postgres-deployment.yaml +``` + +{% hint style="info" %} +**Production Recommendation:** Use managed PostgreSQL services (Amazon RDS, Azure Database for PostgreSQL, Google Cloud SQL) instead of self-hosting in Kubernetes for better reliability, automated backups, and reduced operational overhead. +{% endhint %} + +### Step 2: Update Elsa Server Configuration + +Changing the connection string alone is **not enough**. You must also configure the persistence provider in the Elsa modules. + +#### Option A: Environment Variables + +Update the Elsa Server deployment to use PostgreSQL: + +**elsa-server-deployment.yaml:** +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: elsa-server +spec: + replicas: 1 # Start with 1 for testing. Production: 3+ for HA and rolling updates + selector: + matchLabels: + app: elsa-server + template: + metadata: + labels: + app: elsa-server + spec: + containers: + - name: elsa-server + image: elsaworkflows/elsa-server:latest + env: + # Connection string + - name: ConnectionStrings__PostgreSql + value: "Host=postgres;Database=elsa_workflows;Username=elsa_user;Password=$(POSTGRES_PASSWORD)" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: password + + # Persistence configuration + - name: Elsa__Persistence__Provider + value: "PostgreSql" + - name: Elsa__Persistence__ConnectionStringName + value: "PostgreSql" + + # Module configuration + - name: Elsa__Modules__Management__Persistence__Provider + value: "EntityFrameworkCore.PostgreSql" + - name: Elsa__Modules__Runtime__Persistence__Provider + value: "EntityFrameworkCore.PostgreSql" + + ports: + - containerPort: 8080 + - containerPort: 8081 +``` + +#### Option B: ConfigMap and appsettings.json + +Mount a ConfigMap as `appsettings.Production.json`: + +**elsa-configmap.yaml:** +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: elsa-config +data: + appsettings.Production.json: | + { + "ConnectionStrings": { + "PostgreSql": "Host=postgres;Database=elsa_workflows;Username=elsa_user;Password=$(POSTGRES_PASSWORD)" + }, + "Elsa": { + "Modules": { + "Management": { + "Persistence": { + "Provider": "EntityFrameworkCore.PostgreSql", + "ConnectionStringName": "PostgreSql" + } + }, + "Runtime": { + "Persistence": { + "Provider": "EntityFrameworkCore.PostgreSql", + "ConnectionStringName": "PostgreSql" + } + } + } + } + } +``` + +**Deployment with ConfigMap:** +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: elsa-server +spec: + template: + spec: + containers: + - name: elsa-server + image: elsaworkflows/elsa-server:latest + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: password + volumeMounts: + - name: config + mountPath: /app/appsettings.Production.json + subPath: appsettings.Production.json + volumes: + - name: config + configMap: + name: elsa-config +``` + +### Step 3: Configure Persistence in Program.cs + +If you're building a custom Elsa Server image, configure PostgreSQL persistence in `Program.cs`: + +```csharp +using Elsa.EntityFrameworkCore.Extensions; +using Elsa.EntityFrameworkCore.Modules.Management; +using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddElsa(elsa => +{ + // Configure Management module with PostgreSQL + elsa.UseWorkflowManagement(management => + { + management.UseEntityFrameworkCore(ef => + { + ef.UsePostgreSql( + builder.Configuration.GetConnectionString("PostgreSql"), + options => + { + options.MigrationsHistoryTable("__EFMigrationsHistory_Management"); + } + ); + }); + }); + + // Configure Runtime module with PostgreSQL + elsa.UseWorkflowRuntime(runtime => + { + runtime.UseEntityFrameworkCore(ef => + { + ef.UsePostgreSql( + builder.Configuration.GetConnectionString("PostgreSql"), + options => + { + options.MigrationsHistoryTable("__EFMigrationsHistory_Runtime"); + } + ); + }); + }); + + elsa.UseWorkflowsApi(); + elsa.UseHttp(); +}); + +var app = builder.Build(); + +// Auto-migrate on startup (optional, can also use init containers) +if (builder.Configuration.GetValue("Elsa:AutoMigrate", false)) +{ + await app.Services.MigrateElsaDatabaseAsync(); +} + +app.UseWorkflowsApi(); +app.Run(); +``` + +## Why Changing Only the Connection String Isn't Enough + +A common mistake is to update the connection string but forget to configure the persistence provider. This leads to: + +**Symptoms:** +- Elsa still creates `elsa.db` file (SQLite) +- Connection string is ignored +- Data not persisted to PostgreSQL + +**Root Cause:** + +Elsa modules have **default persistence providers** built into the code. Simply changing the connection string doesn't change the provider. You must explicitly configure each module: + +```csharp +// ❌ Wrong: Only changing connection string +services.AddElsa(elsa => +{ + elsa.UseWorkflowManagement(); // Uses default provider (SQLite) + elsa.UseWorkflowRuntime(); // Uses default provider (SQLite) +}); + +// ✅ Correct: Configure provider for each module +services.AddElsa(elsa => +{ + elsa.UseWorkflowManagement(management => + { + management.UseEntityFrameworkCore(ef => + ef.UsePostgreSql(connectionString)); + }); + + elsa.UseWorkflowRuntime(runtime => + { + runtime.UseEntityFrameworkCore(ef => + ef.UsePostgreSql(connectionString)); + }); +}); +``` + +**Configuration Points:** +1. **Connection String**: Specifies where to connect +2. **Provider**: Specifies how to connect (SQLite, PostgreSQL, SQL Server, etc.) +3. **Module Configuration**: Each module (Management, Runtime) needs its own provider configuration + +All three must be aligned for PostgreSQL to work. + +## Running Database Migrations + +Before Elsa Server can use PostgreSQL, the database schema must be created. + +### Option 1: Init Container + +Use a Kubernetes init container to run migrations before the main app starts: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: elsa-server +spec: + template: + spec: + initContainers: + - name: migrations + image: elsaworkflows/elsa-server:latest + command: ["/bin/sh"] + args: + - -c + - | + dotnet ef database update --context ManagementElsaDbContext + dotnet ef database update --context RuntimeElsaDbContext + env: + - name: ConnectionStrings__PostgreSql + value: "Host=postgres;Database=elsa_workflows;Username=elsa_user;Password=$(POSTGRES_PASSWORD)" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: password + containers: + - name: elsa-server + # ... main container config ... +``` + +{% hint style="warning" %} +**Important:** The above init container example assumes the Elsa Server image includes the EF Core tooling (`dotnet-ef`). Most production images do **not** include this tool for security and size reasons. +- To run migrations reliably, use a dedicated migration image that includes `dotnet-ef`, or build a custom image for this purpose. +- Alternatively, ensure your main image has the necessary tooling, but this is **not recommended** for production. +{% endhint %} +### Option 2: Auto-Migration on Startup + +Enable auto-migration in the application (simpler but not ideal for production): + +```csharp +// In Program.cs +if (builder.Configuration.GetValue("Elsa:AutoMigrate", false)) +{ + await app.Services.MigrateElsaDatabaseAsync(); +} +``` + +Set the environment variable in the deployment: +```yaml +env: +- name: Elsa__AutoMigrate + value: "true" +``` + +{% hint style="warning" %} +**Production Best Practice:** Run migrations as a separate Job or init container, not on every pod startup. This prevents race conditions when multiple pods start simultaneously. +{% endhint %} + +### Option 3: Kubernetes Job + +Create a one-time migration Job: + +**elsa-migration-job.yaml:** +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: elsa-migrations +spec: + template: + spec: + containers: + - name: migrations + image: elsaworkflows/elsa-server:latest + command: ["/bin/sh", "-c"] + args: + - | + dotnet ef database update --context ManagementElsaDbContext + dotnet ef database update --context RuntimeElsaDbContext + env: + - name: ConnectionStrings__PostgreSql + value: "Host=postgres;Database=elsa_workflows;Username=elsa_user;Password=$(POSTGRES_PASSWORD)" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: password + restartPolicy: OnFailure +``` + +{% hint style="warning" %} +**Important:** Similar to the init container example, this Job assumes the Elsa Server image includes the EF Core tooling (`dotnet-ef`). Most production images do **not** include this tool for security and size reasons. +- To run migrations reliably, use a dedicated migration image that includes `dotnet-ef`, or build a custom image for this purpose. +- Alternatively, ensure your main image has the necessary tooling, but this is **not recommended** for production. +{% endhint %} + +Run the job before deploying Elsa Server: +```bash +kubectl apply -f elsa-migration-job.yaml +kubectl wait --for=condition=complete job/elsa-migrations --timeout=300s +kubectl apply -f elsa-server-deployment.yaml +``` + +## Troubleshooting + +### Problem: Elsa Still Uses SQLite + +**Symptoms:** +- `elsa.db` file created in pod +- PostgreSQL connection string appears in logs but isn't used +- No tables created in PostgreSQL + +**Diagnosis:** +```bash +# Check pod logs +kubectl logs -l app=elsa-server --tail=100 + +# Look for: +# - "Using Sqlite provider" (wrong) +# - "Using PostgreSql provider" (correct) +``` + +**Fix:** + +1. Verify provider configuration in environment variables: + ```yaml + env: + - name: Elsa__Modules__Management__Persistence__Provider + value: "EntityFrameworkCore.PostgreSql" + ``` + +2. Or ensure `appsettings.Production.json` is mounted correctly: + ```bash + kubectl exec -it deployment/elsa-server -- cat /app/appsettings.Production.json + ``` + +3. Check that the PostgreSQL package is included in your Docker image: + ```dockerfile + # In Dockerfile + RUN dotnet add package Elsa.EntityFrameworkCore.PostgreSql + ``` + +### Problem: Connection Refused or Timeout + +**Symptoms:** +``` +Npgsql.NpgsqlException: Connection refused +or +A connection attempt failed because the connected party did not properly respond +``` + +**Diagnosis:** +```bash +# Check if PostgreSQL pod is running +kubectl get pods -l app=postgres + +# Check PostgreSQL service +kubectl get svc postgres + +# Test connection from Elsa Server pod +kubectl exec -it deployment/elsa-server -- /bin/sh +apk add postgresql-client +psql -h postgres -U elsa_user -d elsa_workflows +``` + +**Fix:** + +1. Verify PostgreSQL service name matches connection string: + ```yaml + # Connection string must use service name + Host=postgres # ← Must match service metadata.name + ``` + +2. Ensure PostgreSQL is ready before Elsa Server starts: + ```yaml + # Add readiness probe to postgres deployment + readinessProbe: + exec: + command: ["pg_isready", "-U", "elsa_user"] + initialDelaySeconds: 5 + periodSeconds: 5 + ``` + +3. Check namespace - services in different namespaces require FQDN: + ```yaml + # If postgres is in namespace "database" + Host=postgres.database.svc.cluster.local + ``` + +### Problem: Tables Not Created + +**Symptoms:** +- PostgreSQL connection succeeds +- No error messages in logs +- Queries fail: "relation 'Elsa_WorkflowDefinitions' does not exist" + +**Diagnosis:** +```bash +# Connect to PostgreSQL +kubectl exec -it deployment/postgres -- psql -U elsa_user -d elsa_workflows + +# List tables +\dt + +# Expected tables: +# Elsa_WorkflowDefinitions +# Elsa_WorkflowInstances +# Elsa_ActivityExecutionRecords +# ... and others +``` + +**Fix:** + +1. Ensure migrations ran successfully: + ```bash + # Check migration job logs + kubectl logs job/elsa-migrations + + # Look for: + # "Applying migration '20240101000000_InitialCreate'" + # "Done." + ``` + +2. Manually run migrations if needed: + ```bash + kubectl run -it --rm migrations --image=elsaworkflows/elsa-server:latest \ + --restart=Never \ + --env="ConnectionStrings__PostgreSql=Host=postgres;Database=elsa_workflows;Username=elsa_user;Password=..." \ + -- dotnet ef database update + ``` + +3. Check migration history: + ```sql + SELECT * FROM "__EFMigrationsHistory_Management"; + SELECT * FROM "__EFMigrationsHistory_Runtime"; + ``` + +### Problem: Missing Environment Variables + +**Symptoms:** +- Connection string contains literal `$(POSTGRES_PASSWORD)` instead of actual password +- Authentication failures + +**Diagnosis:** +```bash +# Check environment variables in pod +kubectl exec -it deployment/elsa-server -- printenv | grep -i postgres + +# Should show: +# POSTGRES_PASSWORD=actual_password_here +# ConnectionStrings__PostgreSql=Host=postgres;...Password=actual_password_here +``` + +**Fix:** + +Environment variable substitution in connection strings doesn't happen automatically. Use one of these approaches: + +**Option 1: Reference secret directly in each field:** +```yaml +env: +- name: DB_HOST + value: "postgres" +- name: DB_NAME + value: "elsa_workflows" +- name: DB_USER + value: "elsa_user" +- name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: password +- name: ConnectionStrings__PostgreSql + value: "Host=$(DB_HOST);Database=$(DB_NAME);Username=$(DB_USER);Password=$(DB_PASSWORD)" +``` + +**Option 2: Build connection string in code:** +```csharp +// In Program.cs +var host = builder.Configuration["DB_HOST"]; +var database = builder.Configuration["DB_NAME"]; +var user = builder.Configuration["DB_USER"]; +var password = builder.Configuration["DB_PASSWORD"]; + +var connectionString = $"Host={host};Database={database};Username={user};Password={password}"; + +builder.Services.AddElsa(elsa => +{ + elsa.UseWorkflowManagement(management => + { + management.UseEntityFrameworkCore(ef => ef.UsePostgreSql(connectionString)); + }); + // ... +}); +``` + +### Problem: ConfigMap Not Mounted + +**Symptoms:** +- Settings in ConfigMap not applied +- App uses default configuration + +**Diagnosis:** +```bash +# Check if ConfigMap exists +kubectl get configmap elsa-config -o yaml + +# Verify file is mounted in pod +kubectl exec -it deployment/elsa-server -- ls -la /app/appsettings.Production.json + +# Check file content +kubectl exec -it deployment/elsa-server -- cat /app/appsettings.Production.json +``` + +**Fix:** + +1. Ensure volume mount path is correct: + ```yaml + volumeMounts: + - name: config + mountPath: /app/appsettings.Production.json # Must match app's config path + subPath: appsettings.Production.json + ``` + +2. Set `ASPNETCORE_ENVIRONMENT` to load the file: + ```yaml + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + ``` + +3. Verify ConfigMap is in the same namespace as the deployment. + +## Verifying PostgreSQL is Being Used + +After deploying, confirm that Elsa is using PostgreSQL: + +### 1. Check Logs + +```bash +kubectl logs -l app=elsa-server --tail=50 | grep -i postgres + +# Look for messages like: +# "Using PostgreSQL provider" +# "Executed DbCommand (123ms) [Parameters=[], CommandType='Text']" +``` + +### 2. Check Database Tables + +```bash +kubectl exec -it deployment/postgres -- psql -U elsa_user -d elsa_workflows -c "\dt" + +# Expected output: +# List of relations +# Schema | Name | Type | Owner +# --------+------------------------------+-------+----------- +# public | Elsa_ActivityExecutionRecords| table | elsa_user +# public | Elsa_Bookmarks | table | elsa_user +# public | Elsa_WorkflowDefinitions | table | elsa_user +# public | Elsa_WorkflowInstances | table | elsa_user +``` + +### 3. Create a Test Workflow + +```bash +# Port-forward Elsa Server +kubectl port-forward svc/elsa-server 8080:80 + +# Create a workflow via API +curl -X POST http://localhost:8080/elsa/api/workflow-definitions/execute \ + -H "Content-Type: application/json" \ + -d '{ + "workflow": { + "activities": [ + { + "id": "1", + "type": "WriteLine", + "text": "Hello from Kubernetes!" + } + ] + } + }' + +# Verify workflow instance was persisted +kubectl exec -it deployment/postgres -- psql -U elsa_user -d elsa_workflows \ + -c "SELECT id, status FROM \"Elsa_WorkflowInstances\" ORDER BY created_at DESC LIMIT 1;" +``` + +## Production Best Practices + +### 1. Use Managed PostgreSQL + +- **Amazon RDS for PostgreSQL**: Automated backups, point-in-time recovery, Multi-AZ +- **Azure Database for PostgreSQL**: High availability, automatic patching, geo-replication +- **Google Cloud SQL**: Automated backups, read replicas, automatic failover + +### 2. Separate Database User Permissions + +```sql +-- Create read-only user for monitoring +CREATE USER elsa_readonly WITH PASSWORD 'secure_password'; +GRANT CONNECT ON DATABASE elsa_workflows TO elsa_readonly; +GRANT SELECT ON ALL TABLES IN SCHEMA public TO elsa_readonly; + +-- Create migration user with schema modification rights +CREATE USER elsa_migrations WITH PASSWORD 'secure_password'; +GRANT ALL PRIVILEGES ON DATABASE elsa_workflows TO elsa_migrations; + +-- Application user with limited permissions +CREATE USER elsa_app WITH PASSWORD 'secure_password'; +GRANT CONNECT ON DATABASE elsa_workflows TO elsa_app; +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO elsa_app; +``` + +### 3. Use Connection Pooling + +```yaml +env: +- name: ConnectionStrings__PostgreSql + value: "Host=postgres;Database=elsa_workflows;Username=elsa_user;Password=$(PASSWORD);Pooling=true;MinPoolSize=1;MaxPoolSize=20" +``` + +### 4. Enable TLS for Database Connections + +```yaml +env: +- name: ConnectionStrings__PostgreSql + value: "Host=postgres;Database=elsa_workflows;Username=elsa_user;Password=$(PASSWORD);SSL Mode=Require;Trust Server Certificate=false" +``` + +### 5. Implement Health Checks + +```yaml +livenessProbe: + httpGet: + path: /health/live + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + +readinessProbe: + httpGet: + path: /health/ready + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 +``` + +### 6. Set Resource Limits + +```yaml +resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "1000m" +``` + +### 7. Production Scaling Considerations + +For production deployments, run at least 3 replicas: + +```yaml +spec: + replicas: 3 # Minimum for production +``` + +**Why 3+ replicas?** +- **High Availability**: If one pod fails, others continue serving traffic +- **Rolling Updates**: Allows zero-downtime deployments (update one pod at a time) +- **Load Distribution**: Better distribution of workflow execution across pods +- **Pod Disruption Budget**: Can configure PDB to maintain minimum available pods + +**Scaling Strategy:** +- **Development/Staging**: 1-2 replicas +- **Production (low traffic)**: 3 replicas +- **Production (high traffic)**: 5-10+ replicas with HPA +- **Enterprise**: 10+ replicas with node affinity and pod anti-affinity rules + +For automatic scaling based on CPU/memory usage, see the [Full Kubernetes Deployment Guide](../kubernetes-deployment.md#horizontal-pod-autoscaling). + +## Next Steps + +- **Scale Your Deployment**: Configure [Horizontal Pod Autoscaling](../kubernetes-deployment.md#horizontal-pod-autoscaling) +- **Add Monitoring**: Set up [Prometheus and Grafana](../kubernetes-deployment.md#monitoring-with-prometheus--grafana) +- **Secure Your Cluster**: Configure [authentication and authorization](../security/README.md) +- **Integrate with Studio**: Set up [Blazor Dashboard](../integration/blazor-dashboard.md) +- **Production Hardening**: Follow the [Production Checklist](../kubernetes-deployment.md#production-best-practices) + +## Related Documentation + +- [Full Kubernetes Deployment Guide](../kubernetes-deployment.md) - Complete reference with Helm charts, autoscaling, and monitoring +- [Database Configuration](../../getting-started/database-configuration.md) - Detailed persistence setup +- [Clustering Guide](../clustering/README.md) - Multi-node deployment patterns +- [Security & Authentication](../security/README.md) - Securing your Kubernetes deployment +- [Troubleshooting](../troubleshooting/README.md) - Common issues and solutions + +--- + +**Last Updated:** 2025-12-02 +**Addresses Issues:** #75 diff --git a/guides/integration/README.md b/guides/integration/README.md new file mode 100644 index 0000000..b2d722d --- /dev/null +++ b/guides/integration/README.md @@ -0,0 +1,15 @@ +# Integration + +This section provides guides for integrating Elsa Workflows with other technologies and frameworks. + +## Guides in This Section + +* [Blazor Dashboard](blazor-dashboard.md) - Integrating Elsa Studio with Blazor Server applications, covering hosting patterns and authentication configuration. + +## Related Documentation + +- [Elsa Studio](../../application-types/elsa-studio.md) - Studio application overview +- [Hosting Elsa in an Existing App](../onboarding/hosting-elsa-in-existing-app.md) - General integration guide +- [Security & Authentication](../security/README.md) - Authentication configuration +- [HTTP Workflows](../http-workflows/README.md) - Building HTTP-triggered workflows +- [External Application Interaction](../external-application-interaction.md) - Integrating workflows with external systems diff --git a/guides/integration/blazor-dashboard.md b/guides/integration/blazor-dashboard.md new file mode 100644 index 0000000..b3a9e91 --- /dev/null +++ b/guides/integration/blazor-dashboard.md @@ -0,0 +1,728 @@ +--- +description: >- + Guide to integrating Elsa Studio with Blazor Server applications, covering hosting patterns, authentication configuration, and troubleshooting common issues. +--- + +# Blazor Dashboard Integration + +This guide covers integrating Elsa Studio (the workflow designer UI) with Blazor Server applications. You'll learn different hosting patterns, how to configure authentication, and how to troubleshoot common integration issues. + +## Overview + +Elsa Studio can be integrated with Blazor Server apps in several ways: + +- **Same Process**: Host Elsa Server endpoints and Elsa Studio in the same ASP.NET Core application +- **Separate Services**: Host Elsa Server as a separate service and connect Elsa Studio via HTTP +- **Hybrid**: Mix of both approaches for different environments + +This guide focuses primarily on Blazor Server integration, which provides a simpler hosting model compared to Blazor WebAssembly. + +## Prerequisites + +- ASP.NET Core 8.0+ application +- Blazor Server app (or willingness to add Blazor Server to an existing app) +- Basic understanding of Blazor authentication and authorization +- Elsa Server already set up (see [Hosting Elsa in an Existing App](../onboarding/hosting-elsa-in-existing-app.md)) + +## Hosting Patterns + +### Pattern 1: Single Process (Recommended for Small Teams) + +In this pattern, both Elsa Server (workflow runtime + API) and Elsa Studio (UI) run in the same ASP.NET Core process. + +**Advantages:** +- Simpler deployment (single service) +- Easier authentication setup (shared auth context) +- Lower latency (no network hop between UI and API) +- Suitable for small to medium workloads + +**Disadvantages:** +- UI and runtime share resources (memory, CPU) +- Scaling requires scaling both components together +- UI restarts affect runtime and vice versa + +**Architecture:** +``` +┌─────────────────────────────────────────────┐ +│ Blazor Server Application │ +│ │ +│ ┌─────────────┐ ┌──────────────┐ │ +│ │ Elsa Studio │─────>│ Elsa Server │ │ +│ │ (Blazor) │ API │ (Runtime) │ │ +│ └─────────────┘ └──────────────┘ │ +│ │ │ +│ v │ +│ ┌──────────┐ │ +│ │ Database │ │ +│ └──────────┘ │ +└─────────────────────────────────────────────┘ +``` + +### Pattern 2: Separate Services (Recommended for Production) + +Elsa Server runs as a standalone service, and Elsa Studio connects to it via HTTP. + +**Advantages:** +- Independent scaling (scale runtime without scaling UI) +- Better isolation (UI issues don't affect workflow execution) +- Multiple Studio instances can connect to one Server +- Easier to secure and monitor separately + +**Disadvantages:** +- More complex deployment (two services) +- Network latency between UI and API +- Requires proper authentication/authorization setup +- CORS configuration needed + +**Architecture:** +``` +┌───────────────────┐ ┌───────────────────┐ +│ Elsa Studio │ │ Elsa Server │ +│ (Blazor Server) │─────────>│ (API Service) │ +│ │ HTTP │ │ +└───────────────────┘ └─────────┬─────────┘ + │ + v + ┌──────────┐ + │ Database │ + └──────────┘ +``` + +## Implementation: Single Process Pattern + +### Step 1: Install Required Packages + +```bash +# Add Blazor Server support (if not already present) +dotnet add package Microsoft.AspNetCore.Components.Web + +# Add Elsa Server packages +dotnet add package Elsa +dotnet add package Elsa.Workflows.Runtime +dotnet add package Elsa.Workflows.Api +dotnet add package Elsa.EntityFrameworkCore.PostgreSql # or your chosen provider + +# Add Elsa Studio packages +dotnet add package Elsa.Studio +dotnet add package Elsa.Studio.Core.BlazorWasm +``` + +### Step 2: Configure Services in Program.cs + +```csharp +using Elsa.Extensions; +using Elsa.Studio.Extensions; +using Microsoft.AspNetCore.Components; + +var builder = WebApplication.CreateBuilder(args); + +// Add Blazor Server +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +// Add your existing services +builder.Services.AddControllersWithViews(); + +// Add Elsa Server (workflow runtime and API) +builder.Services.AddElsa(elsa => +{ + elsa + .UseWorkflowManagement(management => + { + management.UseEntityFrameworkCore(ef => + ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase"))); + }) + .UseWorkflowRuntime(runtime => + { + runtime.UseEntityFrameworkCore(ef => + ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase"))); + }) + .UseWorkflowsApi() + .UseHttp(); +}); + +// Add Elsa Studio +builder.Services.AddElsaStudio(studio => +{ + // Configure Studio to connect to local Elsa Server + studio.ConfigureHttpClient(options => + { + options.BaseAddress = new Uri("https://localhost:5001"); // Same app + }); +}); + +var app = builder.Build(); + +// Configure middleware pipeline +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} +else +{ + app.UseExceptionHandler("/Error"); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseRouting(); + +// Authentication and authorization +app.UseAuthentication(); +app.UseAuthorization(); + +// Map Elsa API endpoints under /elsa +app.UseWorkflowsApi(); + +// Map Blazor hub and Studio UI +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); // Or your Blazor root page + +app.Run(); +``` + +### Step 3: Create Blazor Host Page + +Create or update `Pages/_Host.cshtml`: + +```html +@page "/" +@namespace YourApp.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + Workflow Designer + + + + + + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + + + + +``` + +### Step 4: Configure App.razor + +Update `App.razor` to include Elsa Studio routes: + +```razor +@using Elsa.Studio.Core.BlazorWasm + + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
+``` + +### Step 5: Test the Integration + +1. Run your application +2. Navigate to `/workflows` (or the Studio route configured) +3. You should see the Elsa Studio workflow designer + +## Authentication Configuration + +When hosting Elsa Server and Studio together, authentication must be configured so that: +1. Users can log into the Blazor app +2. Studio API calls to Elsa Server are authorized + +### Cookie-Based Authentication (Recommended for Single Process) + +This is the simplest approach when both components are in the same process: + +```csharp +using Microsoft.AspNetCore.Authentication.Cookies; + +var builder = WebApplication.CreateBuilder(args); + +// Configure cookie authentication +builder.Services + .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.LoginPath = "/Account/Login"; + options.LogoutPath = "/Account/Logout"; + options.AccessDeniedPath = "/Account/AccessDenied"; + options.ExpireTimeSpan = TimeSpan.FromHours(8); + options.SlidingExpiration = true; + }); + +builder.Services.AddAuthorization(options => +{ + // Define policies for workflow management + options.AddPolicy("WorkflowDesigner", policy => + policy.RequireRole("WorkflowAdmin", "WorkflowDesigner")); + + options.AddPolicy("WorkflowViewer", policy => + policy.RequireRole("WorkflowAdmin", "WorkflowDesigner", "WorkflowViewer")); +}); + +// Add Blazor and Elsa +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +builder.Services.AddElsa(elsa => +{ + // Elsa Server configuration + elsa + .UseIdentity(identity => + { + // Use ASP.NET Core authentication + identity.UseAspNetIdentity(); + }) + .UseDefaultAuthentication() + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); + +builder.Services.AddElsaStudio(); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); +app.UseWorkflowsApi(); +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +app.Run(); +``` + +With this configuration: +- Users log in via your app's login page +- Authentication cookie is automatically sent with Studio → Elsa Server API calls +- Elsa Server validates the cookie and authorizes requests + +### Common Authentication Issues + +#### Issue 1: 401 Unauthorized on API Calls + +**Symptom:** Elsa Studio loads, but API calls to fetch workflow definitions fail with 401 Unauthorized. + +**Cause:** Authentication scheme mismatch or missing authentication middleware. + +**Solution:** + +1. Ensure authentication middleware is added **before** authorization: + ```csharp + app.UseAuthentication(); // Must come first + app.UseAuthorization(); + ``` + +2. Verify the same authentication scheme is used: + ```csharp + // Both must use the same scheme (e.g., Cookies) + builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme); + + builder.Services.AddElsa(elsa => + { + elsa.UseDefaultAuthentication(); // Uses default ASP.NET Core auth + }); + ``` + +3. Check that cookies are being sent in Studio API calls (browser DevTools → Network tab) + +#### Issue 2: Infinite Login Redirect Loop + +**Symptom:** Navigating to Studio redirects to login, which redirects back to Studio, which redirects to login again. + +**Cause:** Login path is not excluded from authorization requirements. + +**Solution:** + +Allow anonymous access to login/logout pages: + +```csharp +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapRazorPages() + .RequireAuthorization(); // Require auth for all pages + +// Except login/logout +app.MapRazorPages() + .AllowAnonymous() + .WithName("Account"); // Pages under /Account allow anonymous +``` + +Or use `[AllowAnonymous]` attribute on login page: + +```csharp +[AllowAnonymous] +public class LoginModel : PageModel +{ + // ... +} +``` + +#### Issue 3: Missing Bearer Token in API Calls + +**Symptom:** In a separate services setup, Studio doesn't send authentication token to Elsa Server. + +**Cause:** Token forwarding not configured. + +**Solution:** + +Configure Studio to forward authentication: + +```csharp +builder.Services.AddElsaStudio(studio => +{ + studio.ConfigureHttpClient(options => + { + options.BaseAddress = new Uri("https://elsa-server.example.com"); + }); + + // Forward authentication token + studio.ConfigureHttpClient((sp, client) => + { + var httpContextAccessor = sp.GetRequiredService(); + var token = httpContextAccessor.HttpContext?.Request.Headers["Authorization"].ToString(); + + if (!string.IsNullOrEmpty(token)) + { + client.DefaultRequestHeaders.Add("Authorization", token); + } + }); +}); +``` + +## Implementation: Separate Services Pattern + +When running Elsa Server and Studio as separate services, additional configuration is required. + +### Elsa Server Configuration + +```csharp +// Elsa Server (standalone API service) +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddElsa(elsa => +{ + elsa + .UseIdentity(identity => + { + identity.UseConfigurationBasedIdentityProvider(); + }) + .UseDefaultAuthentication() + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); + +// Configure CORS to allow Studio to call API +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowStudio", policy => + { + policy + .WithOrigins("https://studio.example.com") // Your Studio URL + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); // If using cookies + }); +}); + +var app = builder.Build(); + +app.UseCors("AllowStudio"); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseWorkflowsApi(); + +app.Run(); +``` + +### Elsa Studio Configuration + +```csharp +// Elsa Studio (separate Blazor Server app) +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +builder.Services.AddAuthentication(/* Your auth config */); + +builder.Services.AddElsaStudio(studio => +{ + // Point to remote Elsa Server + studio.ConfigureHttpClient(options => + { + options.BaseAddress = new Uri("https://elsa-server.example.com"); + }); + + // Configure authentication forwarding + studio.ConfigureHttpClient((sp, client) => + { + var httpContextAccessor = sp.GetRequiredService(); + var context = httpContextAccessor.HttpContext; + + if (context?.User?.Identity?.IsAuthenticated == true) + { + // Forward authentication cookie or token + var authHeader = context.Request.Headers["Authorization"].ToString(); + if (!string.IsNullOrEmpty(authHeader)) + { + client.DefaultRequestHeaders.Add("Authorization", authHeader); + } + } + }); +}); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +app.Run(); +``` + +### CORS Configuration + +When Studio and Server are on different domains, configure CORS properly: + +**Elsa Server (appsettings.json):** +```json +{ + "Elsa": { + "Cors": { + "AllowedOrigins": [ + "https://studio.example.com", + "https://localhost:5002" + ] + } + } +} +``` + +**Elsa Server (Program.cs):** +```csharp +var allowedOrigins = builder.Configuration.GetSection("Elsa:Cors:AllowedOrigins").Get(); + +builder.Services.AddCors(options => +{ + options.AddPolicy("ElsaCors", policy => + { + policy + .WithOrigins(allowedOrigins) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); +}); +``` + +## Troubleshooting Common Issues + +### Login/401 Issues + +**Problem:** Studio loads but shows "Unauthorized" or redirects to login repeatedly. + +**Diagnosis:** +1. Check browser DevTools → Network tab +2. Look at API calls from Studio to Elsa Server +3. Check response status codes and headers + +**Common Causes:** + +1. **Mismatched authentication scheme:** + - Studio uses cookies, Server expects Bearer tokens + - Fix: Align both to use the same scheme + +2. **Authentication middleware missing or in wrong order:** + ```csharp + // Wrong order + app.UseAuthorization(); // ❌ Before authentication + app.UseAuthentication(); + + // Correct order + app.UseAuthentication(); // ✅ First + app.UseAuthorization(); + ``` + +3. **CORS blocking credentials:** + ```csharp + // Must include AllowCredentials for cookie forwarding + policy.AllowCredentials(); + ``` + +### Token/Cookie Not Forwarded + +**Problem:** User is authenticated in Studio, but API calls don't include authentication. + +**Solution:** Configure HTTP client to forward authentication: + +```csharp +builder.Services.AddHttpContextAccessor(); + +builder.Services.AddElsaStudio(studio => +{ + studio.ConfigureHttpClient((sp, client) => + { + var httpContextAccessor = sp.GetRequiredService(); + var httpContext = httpContextAccessor.HttpContext; + + if (httpContext != null) + { + // Forward authorization header (preferred for API calls) + var authHeader = httpContext.Request.Headers["Authorization"].FirstOrDefault(); + if (!string.IsNullOrEmpty(authHeader)) + { + client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", authHeader); + } + + // Forward authentication cookies only to trusted Elsa Server + // Note: Only forward to the same domain or explicitly trusted domains + var cookies = httpContext.Request.Headers["Cookie"].FirstOrDefault(); + if (!string.IsNullOrEmpty(cookies) && IsElsaServerTrusted(client.BaseAddress)) + { + // Filter to only authentication-related cookies if needed + client.DefaultRequestHeaders.TryAddWithoutValidation("Cookie", cookies); + } + } + }); +}); + +// Helper method to validate Elsa Server is trusted +bool IsElsaServerTrusted(Uri baseAddress) +{ + // Only forward cookies to same origin or explicitly configured trusted domains + var trustedDomains = builder.Configuration.GetSection("ElsaServer:TrustedDomains").Get() + ?? Array.Empty(); + + return trustedDomains.Contains(baseAddress.Host, StringComparer.OrdinalIgnoreCase) + || baseAddress.Host == "localhost" + || baseAddress.Host == "127.0.0.1"; +} +``` + +## Minimal Conceptual Example + +Here's a complete minimal example of a Blazor Server app with Elsa Studio: + +**Program.cs:** +```csharp +using Elsa.Extensions; +using Elsa.Studio.Extensions; +using Microsoft.AspNetCore.Authentication.Cookies; + +var builder = WebApplication.CreateBuilder(args); + +// Blazor +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +// Authentication +builder.Services + .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(); + +builder.Services.AddAuthorization(); + +// Elsa Server (local) +builder.Services.AddElsa(elsa => +{ + elsa + .UseDefaultAuthentication() + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); + +// Elsa Studio +builder.Services.AddElsaStudio(studio => +{ + studio.ConfigureHttpClient(options => + { + options.BaseAddress = new Uri("https://localhost:5001"); + }); +}); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseWorkflowsApi(); +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +app.Run(); +``` + +## Security Considerations + +For production deployments, follow security best practices: + +1. **Always use HTTPS**: Never transmit authentication tokens over HTTP +2. **Configure CORS restrictively**: Only allow known Studio origins +3. **Use short-lived tokens**: Configure appropriate token lifetimes +4. **Implement RBAC**: Restrict workflow design to authorized users only +5. **Audit access**: Log all workflow modifications + +For detailed security configuration, see: +- [Security & Authentication Guide](../security/README.md) +- [External Identity Providers](../security/external-identity-providers.md) +- [Disable Auth in Dev](../security/disable-auth.md) (development only) + +## Next Steps + +- **Customize Studio**: Configure themes, localization, and plugins +- **Add Custom Activities**: Extend workflow designer with [Custom Activities](../../extensibility/custom-activities.md) +- **Deploy to Production**: Follow [Kubernetes Deployment](../deployment/kubernetes.md) guide +- **Integrate with Identity Provider**: Set up [External Identity Providers](../security/external-identity-providers.md) +- **Run Workflows**: Learn about [Running Workflows](../running-workflows/README.md) + +## Related Documentation + +- [Hosting Elsa in an Existing App](../onboarding/hosting-elsa-in-existing-app.md) +- [Elsa Studio Application Type](../../application-types/elsa-studio.md) +- [Security & Authentication](../security/README.md) +- [External Identity Providers](../security/external-identity-providers.md) +- [Troubleshooting Guide](../troubleshooting/README.md) + +--- + +**Last Updated:** 2025-12-02 +**Addresses Issues:** #87 diff --git a/guides/onboarding/README.md b/guides/onboarding/README.md new file mode 100644 index 0000000..ba5860b --- /dev/null +++ b/guides/onboarding/README.md @@ -0,0 +1,14 @@ +# Onboarding + +This section provides guides to help you get started with integrating Elsa Workflows into your applications. + +## Guides in This Section + +* [Hosting Elsa in an Existing App](hosting-elsa-in-existing-app.md) - Step-by-step guide to adding Elsa to an existing ASP.NET Core application, including persistence setup and common integration challenges. + +## Related Documentation + +- [Getting Started](../../getting-started/hello-world.md) - Basic tutorials and concepts +- [Application Types](../../application-types/elsa-server.md) - Understanding different Elsa deployment options +- [Database Configuration](../../getting-started/database-configuration.md) - Persistence setup +- [Deployment](../deployment/kubernetes.md) - Production deployment guides diff --git a/guides/onboarding/hosting-elsa-in-existing-app.md b/guides/onboarding/hosting-elsa-in-existing-app.md new file mode 100644 index 0000000..4b04781 --- /dev/null +++ b/guides/onboarding/hosting-elsa-in-existing-app.md @@ -0,0 +1,655 @@ +--- +description: >- + Step-by-step guide to integrating Elsa Workflows into an existing ASP.NET Core application, including persistence setup, common pain points, and troubleshooting. +--- + +# Hosting Elsa in an Existing ASP.NET Core App + +This guide walks you through adding Elsa Workflows to an existing ASP.NET Core application. Whether you're building a new feature or modernizing an existing codebase, this guide will help you integrate Elsa smoothly while avoiding common pitfalls. + +## Overview + +Integrating Elsa into an existing ASP.NET Core app involves: + +1. Installing required NuGet packages +2. Configuring Elsa services in `Program.cs` +3. Setting up persistence (database) +4. Addressing common integration challenges + +This guide focuses on practical integration patterns and addresses common issues reported by the community (issue #6). + +## Prerequisites + +Before you begin, ensure you have: + +- An existing ASP.NET Core application (.NET 6.0+, .NET 8.0 recommended) +- Basic understanding of ASP.NET Core dependency injection +- A database server (PostgreSQL, SQL Server, SQLite, or MySQL) +- Visual Studio 2022+, Visual Studio Code, or Rider + +## Step 1: Install Elsa Packages + +Add the core Elsa packages to your project. The exact packages depend on your needs: + +### Basic Workflow Runtime + +For workflow execution without a UI: + +```bash +dotnet add package Elsa +dotnet add package Elsa.Workflows.Runtime +dotnet add package Elsa.Workflows.Api +``` + +### With Entity Framework Core Persistence + +For PostgreSQL: + +```bash +dotnet add package Elsa.EntityFrameworkCore.PostgreSql +``` + +For SQL Server: + +```bash +dotnet add package Elsa.EntityFrameworkCore.SqlServer +``` + +For SQLite (development only): + +```bash +dotnet add package Elsa.EntityFrameworkCore.Sqlite +``` + +### Optional: HTTP Activities + +If your workflows need HTTP endpoints: + +```bash +dotnet add package Elsa.Http +``` + +### Optional: Elsa Studio (Web UI) + +To include the workflow designer UI in your app: + +```bash +dotnet add package Elsa.Studio +dotnet add package Elsa.Studio.Core.BlazorWasm +``` + +## Step 2: Configure Elsa in Program.cs + +Add Elsa to your existing `Program.cs` configuration. Here's a complete example showing integration with an existing app: + +### Basic Configuration + +```csharp +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +// Your existing services +builder.Services.AddControllers(); +builder.Services.AddRazorPages(); +// ... other services ... + +// Add Elsa services +builder.Services.AddElsa(elsa => +{ + // Configure workflow management (designer, definitions) + elsa.UseWorkflowManagement(); + + // Configure workflow runtime (execution engine) + elsa.UseWorkflowRuntime(runtime => + { + runtime.WorkflowInboxCleanupOptions = new() + { + // Clean up completed workflow instances after 30 days + BatchSize = 100, + SweepInterval = TimeSpan.FromMinutes(60) + }; + }); + + // Expose workflows via REST API + elsa.UseWorkflowsApi(); + + // Add HTTP activities for workflow endpoints + elsa.UseHttp(); +}); + +var app = builder.Build(); + +// Your existing middleware +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + +// Map Elsa workflows API endpoints +app.UseWorkflowsApi(); + +// Your existing endpoints +app.MapControllers(); +app.MapRazorPages(); + +app.Run(); +``` + +### With Persistence (PostgreSQL Example) + +```csharp +using Elsa.EntityFrameworkCore.Extensions; +using Elsa.EntityFrameworkCore.Modules.Management; +using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +// Your existing services +builder.Services.AddControllers(); + +// Add Elsa with PostgreSQL persistence +builder.Services.AddElsa(elsa => +{ + // Use PostgreSQL for workflow definitions and instances + elsa.UseWorkflowManagement(management => + { + management.UseEntityFrameworkCore(ef => + { + ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase")); + }); + }); + + elsa.UseWorkflowRuntime(runtime => + { + runtime.UseEntityFrameworkCore(ef => + { + ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase")); + }); + }); + + elsa.UseWorkflowsApi(); + elsa.UseHttp(); +}); + +var app = builder.Build(); + +// Run EF Core migrations on startup (development only) +// For production, run migrations separately +if (app.Environment.IsDevelopment()) +{ + await app.Services.MigrateElsaDatabaseAsync(); +} + +app.UseHttpsRedirection(); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseWorkflowsApi(); +app.MapControllers(); + +app.Run(); +``` + +**Connection String (appsettings.json):** + +```json +{ + "ConnectionStrings": { + "ElsaDatabase": "Host=localhost;Database=elsa_workflows;Username=elsa;Password=your_secure_password" + } +} +``` + +### With SQL Server + +```csharp +elsa.UseWorkflowManagement(management => +{ + management.UseEntityFrameworkCore(ef => + { + ef.UseSqlServer(builder.Configuration.GetConnectionString("ElsaDatabase")); + }); +}); + +elsa.UseWorkflowRuntime(runtime => +{ + runtime.UseEntityFrameworkCore(ef => + { + ef.UseSqlServer(builder.Configuration.GetConnectionString("ElsaDatabase")); + }); +}); +``` + +**SQL Server Connection String:** + +```json +{ + "ConnectionStrings": { + "ElsaDatabase": "Server=localhost;Database=ElsaWorkflows;User Id=elsa_user;Password=your_secure_password;TrustServerCertificate=true" + } +} +``` + +## Step 3: Initialize Database + +After configuring persistence, you need to create the database schema. + +### Option A: Auto-Migration (Development) + +For development environments, you can auto-migrate on startup: + +```csharp +if (app.Environment.IsDevelopment()) +{ + await app.Services.MigrateElsaDatabaseAsync(); +} +``` + +This creates/updates the database schema automatically when the app starts. + +### Option B: Manual Migration (Production) + +For production, run migrations separately using the EF Core CLI: + +```bash +# Install EF Core tools if not already installed +dotnet tool install --global dotnet-ef + +# Add a migration +dotnet ef migrations add InitialElsa --context ManagementElsaDbContext +dotnet ef migrations add InitialElsaRuntime --context RuntimeElsaDbContext + +# Apply migrations +dotnet ef database update --context ManagementElsaDbContext +dotnet ef database update --context RuntimeElsaDbContext +``` + +{% hint style="info" %} +**Note:** Elsa uses separate DbContexts for management (workflow definitions) and runtime (workflow instances). You'll need to manage migrations for both contexts. +{% endhint %} + +## Common Pain Points and Solutions + +Based on community feedback (issue #6), here are the most common integration challenges and how to solve them: + +### 1. DbContextOptions Registration Issue + +**Problem:** When you have your own `AppDbContext` that requires `DbContextOptions`, you may encounter conflicts with Elsa's internal DbContext registration. + +**Symptoms:** +``` +System.InvalidOperationException: Unable to resolve service for type +'Microsoft.EntityFrameworkCore.DbContextOptions`1[YourApp.Data.AppDbContext]' +while attempting to activate 'YourApp.Data.AppDbContext'. +``` + +**Solution:** + +Explicitly register your `AppDbContext` with its own connection string and options: + +```csharp +// Register your own DbContext first +builder.Services.AddDbContext(options => +{ + options.UseNpgsql(builder.Configuration.GetConnectionString("AppDatabase")); + // Your DbContext configuration +}); + +// Then register Elsa with a different connection string +builder.Services.AddElsa(elsa => +{ + elsa.UseWorkflowManagement(management => + { + management.UseEntityFrameworkCore(ef => + { + // Use separate connection string for Elsa + ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase")); + }); + }); + + elsa.UseWorkflowRuntime(runtime => + { + runtime.UseEntityFrameworkCore(ef => + { + ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase")); + }); + }); +}); +``` + +**Key Points:** +- Use separate databases or schemas for Elsa and your app +- Elsa's DbContexts are registered with specific lifetimes - don't try to share them +- If you must share a database, use different connection strings with schema prefixes + +### 2. Version Pinning Conflicts + +**Problem:** Elsa depends on specific versions of packages like `Hangfire`, `Microsoft.EntityFrameworkCore.Design`, or `Microsoft.CodeAnalysis.*`, which may conflict with your existing dependencies. + +**Symptoms:** +``` +NU1605: Detected package downgrade: Microsoft.EntityFrameworkCore from 8.0.0 to 7.0.5 +NU1608: Detected package version outside of dependency constraint +``` + +**Solutions:** + +#### Strategy 1: Version Alignment + +Align your EF Core versions with Elsa's requirements: + +```xml + + + + + + + + + + +``` + +#### Strategy 2: Binding Redirects (Framework Apps) + +For .NET Framework apps, use binding redirects in `web.config`: + +```xml + + + + + + + + +``` + +#### Strategy 3: Update Dependencies + +Update your existing packages to match Elsa's requirements: + +```bash +# Update all EF Core packages +dotnet add package Microsoft.EntityFrameworkCore --version 8.0.0 +dotnet add package Microsoft.EntityFrameworkCore.Design --version 8.0.0 +dotnet add package Microsoft.EntityFrameworkCore.Tools --version 8.0.0 + +# Update Hangfire if used +dotnet add package Hangfire.Core --version 1.8.0 +dotnet add package Hangfire.AspNetCore --version 1.8.0 +``` + +### 3. Swagger / Swashbuckle Schema Conflicts + +**Problem:** When adding Swagger/Swashbuckle to document your API, you may encounter schema ID conflicts with Elsa's API endpoints. + +**Symptoms:** +``` +Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: +Conflicting schemaIds: Duplicate schemaIds detected for types +Elsa.Workflows.Core.Models.WorkflowDefinition and YourApp.Models.WorkflowDefinition +``` + +**Solutions:** + +#### Solution 1: Custom Schema ID Generation + +Configure Swashbuckle to generate unique schema IDs: + +```csharp +builder.Services.AddSwaggerGen(options => +{ + options.CustomSchemaIds(type => + { + // Include namespace to avoid conflicts + return type.FullName?.Replace("+", "."); + }); + + // Or use a more sophisticated approach + options.CustomSchemaIds(type => + { + if (type.FullName?.StartsWith("Elsa") == true) + { + return "Elsa_" + type.Name; + } + return type.Name; + }); +}); +``` + +#### Solution 2: Exclude Elsa Endpoints from Swagger + +If you don't need to document Elsa's API endpoints: + +```csharp +builder.Services.AddSwaggerGen(options => +{ + options.DocInclusionPredicate((docName, apiDesc) => + { + // Exclude Elsa API endpoints from Swagger documentation + var actionDescriptor = apiDesc.ActionDescriptor; + var controllerName = actionDescriptor.RouteValues["controller"]; + + if (controllerName?.StartsWith("Elsa") == true) + { + return false; + } + + return true; + }); +}); +``` + +#### Solution 3: Multiple Swagger Documents + +Create separate Swagger documents for your API and Elsa: + +```csharp +builder.Services.AddSwaggerGen(options => +{ + // Your API documentation + options.SwaggerDoc("v1", new OpenApiInfo + { + Title = "My App API", + Version = "v1" + }); + + // Elsa API documentation + options.SwaggerDoc("elsa", new OpenApiInfo + { + Title = "Elsa Workflows API", + Version = "v1" + }); + + options.DocInclusionPredicate((docName, apiDesc) => + { + var controllerName = apiDesc.ActionDescriptor.RouteValues["controller"]; + + if (docName == "elsa") + { + return controllerName?.StartsWith("Elsa") == true; + } + + return controllerName?.StartsWith("Elsa") != true; + }); +}); + +// In the middleware pipeline +app.UseSwagger(); +app.UseSwaggerUI(options => +{ + options.SwaggerEndpoint("/swagger/v1/swagger.json", "My App API"); + options.SwaggerEndpoint("/swagger/elsa/swagger.json", "Elsa Workflows API"); +}); +``` + +## Authentication and Authorization + +For production deployments, you'll need to secure Elsa's API endpoints. This section provides a high-level overview; see the [Security & Authentication Guide](../security/README.md) for detailed configuration. + +### Quick Overview + +**Development (Disable Auth):** +- See [Disable Authentication in Dev](../security/disable-auth.md) +- Not recommended for production + +**Production Options:** +- **Elsa.Identity**: Built-in identity system with user management +- **API Keys**: Simple token-based authentication +- **OIDC/OAuth2**: Integration with Azure AD, Auth0, Keycloak, etc. +- See [External Identity Providers](../security/external-identity-providers.md) + +### Basic Identity Setup + +```csharp +builder.Services.AddElsa(elsa => +{ + elsa + .UseIdentity(identity => + { + identity.UseConfigurationBasedIdentityProvider(); + }) + .UseDefaultAuthentication() + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); + +// Enable authentication middleware +app.UseAuthentication(); +app.UseAuthorization(); +``` + +## Testing Your Integration + +### 1. Verify Elsa Services are Registered + +Create a simple controller to check if Elsa is loaded: + +```csharp +using Elsa.Workflows.Runtime; +using Microsoft.AspNetCore.Mvc; + +[ApiController] +[Route("api/[controller]")] +public class HealthController : ControllerBase +{ + private readonly IWorkflowRuntime _workflowRuntime; + + public HealthController(IWorkflowRuntime workflowRuntime) + { + _workflowRuntime = workflowRuntime; + } + + [HttpGet("elsa")] + public IActionResult ElsaHealth() + { + return Ok(new + { + elsaLoaded = _workflowRuntime != null, + message = "Elsa Workflows is integrated successfully" + }); + } +} +``` + +### 2. Check API Endpoints + +With the app running, navigate to: + +- `https://localhost:5001/elsa/api/workflow-definitions` - List workflow definitions +- `https://localhost:5001/swagger` - Swagger UI (if configured) + +### 3. Verify Database + +Check that Elsa tables were created: + +**PostgreSQL:** +```sql +SELECT table_name +FROM information_schema.tables +WHERE table_schema = 'public' + AND table_name LIKE 'Elsa%'; +``` + +**SQL Server:** +```sql +SELECT TABLE_NAME +FROM INFORMATION_SCHEMA.TABLES +WHERE TABLE_NAME LIKE 'Elsa%'; +``` + +You should see tables like: +- `Elsa_WorkflowDefinitions` +- `Elsa_WorkflowInstances` +- `Elsa_Bookmarks` +- And others + +## Next Steps + +Now that Elsa is integrated into your app: + +1. **Create your first workflow**: Use [Elsa Studio](../../application-types/elsa-studio.md) or programmatic workflow definitions +2. **Add workflow activities**: Extend Elsa with [Custom Activities](../../extensibility/custom-activities.md) +3. **Secure your deployment**: Configure [authentication and authorization](../security/README.md) +4. **Deploy to production**: Follow the [Kubernetes Deployment Guide](../deployment/kubernetes.md) or [Clustering Guide](../clustering/README.md) +5. **Monitor workflows**: Set up observability and logging + +## Related Documentation + +- [Elsa Server Setup](../../application-types/elsa-server.md) +- [Database Configuration](../../getting-started/database-configuration.md) +- [Persistence Guide](../persistence/README.md) +- [Security & Authentication](../security/README.md) +- [Blazor Dashboard Integration](../integration/blazor-dashboard.md) +- [Troubleshooting Guide](../troubleshooting/README.md) + +## Troubleshooting + +### Services Not Resolving + +**Problem:** `IWorkflowRuntime` or other Elsa services not found in DI. + +**Solution:** Ensure you called `AddElsa()` before `builder.Build()`: + +```csharp +builder.Services.AddElsa(elsa => { /* config */ }); +var app = builder.Build(); // After AddElsa +``` + +### Database Connection Fails + +**Problem:** `Npgsql.NpgsqlException: Connection refused` + +**Solutions:** +- Verify database server is running +- Check connection string format +- Ensure firewall allows connections +- Test connection with `psql` or `sqlcmd` + +### Migrations Not Applied + +**Problem:** Tables not created after running migrations. + +**Solution:** Ensure migrations were created for both contexts: + +```bash +dotnet ef migrations add InitialElsa --context ManagementElsaDbContext +dotnet ef migrations add InitialElsaRuntime --context RuntimeElsaDbContext +``` + +Then apply both: + +```bash +dotnet ef database update --context ManagementElsaDbContext +dotnet ef database update --context RuntimeElsaDbContext +``` + +--- + +**Last Updated:** 2025-12-02 +**Addresses Issues:** #6 diff --git a/guides/security/disable-auth.md b/guides/security/disable-auth.md new file mode 100644 index 0000000..d12bf14 --- /dev/null +++ b/guides/security/disable-auth.md @@ -0,0 +1,529 @@ +--- +description: >- + Guide for disabling authentication in Elsa Server and Studio during development to simplify local testing and experimentation. +--- + +# Disable Authentication in Development + +This guide explains how to disable authentication and authorization in Elsa Server and Studio for **development and testing purposes only**. Disabling authentication simplifies local development, prototyping, and learning Elsa without the complexity of setting up identity providers. + +{% hint style="danger" %} +**WARNING: NOT FOR PRODUCTION USE** + +Never deploy Elsa to production with authentication disabled. This would expose your workflow management APIs and allow anyone to: +- View, create, modify, and delete workflows +- Execute workflows with arbitrary payloads +- Access sensitive workflow data and variables +- Disrupt or manipulate running workflow instances + +Always enable proper authentication and authorization before deploying to production environments. +{% endhint %} + +## When to Disable Authentication + +Disabling authentication is appropriate for: + +- **Local development**: Testing workflows on your development machine +- **Learning Elsa**: Exploring features without authentication complexity +- **Proof of concepts**: Quick prototypes and demos +- **Integration tests**: Automated testing without auth overhead +- **Docker Compose local stacks**: Development containers on localhost + +## When NOT to Disable Authentication + +Never disable authentication for: + +- **Production deployments**: Any environment accessible outside your local machine +- **Staging environments**: Pre-production testing should mirror production security +- **Shared development environments**: Multiple developers or accessible from network +- **Cloud deployments**: Any deployment to AWS, Azure, GCP, or other cloud platforms +- **Kubernetes clusters**: Even development clusters should have basic auth + +## Methods for Disabling Authentication + +There are several approaches to disable authentication in Elsa, depending on your setup and requirements. + +### Method 1: Disable Endpoint Security (Simplest) + +This is the easiest method and disables authentication for all Elsa API endpoints. + +**Program.cs:** +```csharp +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +// ⚠️ DEVELOPMENT ONLY: Disable all endpoint security +if (builder.Environment.IsDevelopment()) +{ + Elsa.Api.Common.Options.EndpointSecurityOptions.DisableSecurity(); +} + +builder.Services.AddElsa(elsa => +{ + elsa + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi() + .UseHttp(); +}); + +var app = builder.Build(); + +app.UseWorkflowsApi(); +app.Run(); +``` + +**Key Points:** +- `DisableSecurity()` removes all authorization requirements from Elsa API endpoints +- Wrap in `if (builder.Environment.IsDevelopment())` to prevent accidental production use +- No authentication middleware needed + +### Method 2: Bypass Authorization with AllowAnonymous + +Configure authorization policies to allow all requests: + +**Program.cs:** +```csharp +using Elsa.Extensions; +using Microsoft.AspNetCore.Authorization; + +var builder = WebApplication.CreateBuilder(args); + +if (builder.Environment.IsDevelopment()) +{ + // Allow all requests without authentication + builder.Services.AddAuthorization(options => + { + options.FallbackPolicy = new AuthorizationPolicyBuilder() + .RequireAssertion(_ => true) // Always allow + .Build(); + }); +} + +builder.Services.AddElsa(elsa => +{ + elsa + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); + +var app = builder.Build(); + +// Still add middleware, but policy allows everything +app.UseAuthentication(); +app.UseAuthorization(); + +app.UseWorkflowsApi(); +app.Run(); +``` + +### Method 3: Disable Elsa Identity Module + +If you've configured Elsa.Identity, you can disable it for development: + +**Program.cs:** +```csharp +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddElsa(elsa => +{ + // Don't call UseIdentity() or UseDefaultAuthentication() in development + + if (builder.Environment.IsProduction()) + { + // Only enable identity in production + elsa.UseIdentity(identity => + { + identity.UseConfigurationBasedIdentityProvider(); + }); + elsa.UseDefaultAuthentication(); + } + + elsa + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); + +var app = builder.Build(); + +// Only use auth middleware in production +if (builder.Environment.IsProduction()) +{ + app.UseAuthentication(); + app.UseAuthorization(); +} + +app.UseWorkflowsApi(); +app.Run(); +``` + +### Method 4: Configuration-Based Toggle + +Use configuration files to toggle authentication: + +**appsettings.Development.json:** +```json +{ + "Elsa": { + "Security": { + "Enabled": false + } + } +} +``` + +**appsettings.Production.json:** +```json +{ + "Elsa": { + "Security": { + "Enabled": true + } + } +} +``` + +**Program.cs:** +```csharp +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +var securityEnabled = builder.Configuration.GetValue("Elsa:Security:Enabled", true); + +if (!securityEnabled) +{ + Elsa.Api.Common.Options.EndpointSecurityOptions.DisableSecurity(); +} + +builder.Services.AddElsa(elsa => +{ + if (securityEnabled) + { + elsa + .UseIdentity(identity => + { + identity.UseConfigurationBasedIdentityProvider(); + }) + .UseDefaultAuthentication(); + } + + elsa + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); + +var app = builder.Build(); + +if (securityEnabled) +{ + app.UseAuthentication(); + app.UseAuthorization(); +} + +app.UseWorkflowsApi(); +app.Run(); +``` + +## Disabling Authentication in Elsa Studio + +When disabling authentication in Elsa Server, you also need to configure Elsa Studio to not send authentication credentials. + +### Studio Configuration + +**Program.cs (Studio app):** +```csharp +using Elsa.Studio.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +if (builder.Environment.IsDevelopment()) +{ + // Disable Studio authorization checks + builder.Services.AddElsaStudio(studio => + { + studio.ConfigureHttpClient(options => + { + options.BaseAddress = new Uri("https://localhost:5001"); + // No authentication configuration needed + }); + }); + + // Optional: Disable authorization on Studio pages + builder.Services.Configure(options => + { + options.DetailedErrors = true; + }); +} +else +{ + // Production: Enable authentication + builder.Services.AddAuthentication(/* ... */); + builder.Services.AddElsaStudio(studio => + { + studio.ConfigureHttpClient(options => + { + options.BaseAddress = new Uri("https://elsa-server.example.com"); + }); + // Configure authentication forwarding + }); +} + +var app = builder.Build(); + +if (!builder.Environment.IsDevelopment()) +{ + app.UseAuthentication(); + app.UseAuthorization(); +} + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); +app.Run(); +``` + +## Docker Compose Example + +For local development with Docker Compose, disable authentication in both Server and Studio: + +**docker-compose.yml:** +```yaml +version: '3.8' + +services: + elsa-server: + image: elsaworkflows/elsa-server:latest + environment: + - ASPNETCORE_ENVIRONMENT=Development + - Elsa__Security__Enabled=false + - ConnectionStrings__PostgreSql=Host=postgres;Database=elsa;Username=elsa;Password=elsa123 + ports: + - "5001:8080" + depends_on: + - postgres + + elsa-studio: + image: elsaworkflows/elsa-studio-wasm:latest + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ElsaServer__Url=http://elsa-server:8080 + ports: + - "5002:8080" + depends_on: + - elsa-server + + postgres: + image: postgres:16 + environment: + - POSTGRES_DB=elsa + - POSTGRES_USER=elsa + - POSTGRES_PASSWORD=elsa123 + volumes: + - postgres-data:/var/lib/postgresql/data + +volumes: + postgres-data: +``` + +## Testing with Disabled Authentication + +Once authentication is disabled, you can access Elsa APIs directly: + +### Test API Access + +```bash +# List workflow definitions (no auth header needed) +curl http://localhost:5001/elsa/api/workflow-definitions + +# Create a workflow +curl -X POST http://localhost:5001/elsa/api/workflow-definitions \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Test Workflow", + "publish": true + }' + +# Execute a workflow +curl -X POST http://localhost:5001/elsa/api/workflow-definitions/{id}/execute +``` + +### Test Studio Access + +Navigate to Studio in your browser: +``` +http://localhost:5002/workflows +``` + +You should be able to: +- View all workflows +- Create and edit workflows +- Execute workflows +- View workflow instances + +All without logging in. + +## Security Considerations for Development + +Even with authentication disabled in development, follow these practices: + +### 1. Restrict Network Access + +**Bind to localhost only:** +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Only listen on localhost in development +if (builder.Environment.IsDevelopment()) +{ + builder.WebHost.UseUrls("http://localhost:5001", "https://localhost:5002"); +} +``` + +**Docker Compose (localhost only):** +```yaml +services: + elsa-server: + ports: + - "127.0.0.1:5001:8080" # Bind to localhost only +``` + +### 2. Use Separate Development Database + +Never point development environments to production databases: + +```json +{ + "ConnectionStrings": { + "ElsaDatabase": "Host=localhost;Database=elsa_dev;Username=dev_user;Password=dev_password" + } +} +``` + +### 3. Firewall Rules + +Ensure development machines have firewall rules blocking external access to Elsa ports. + +### 4. Environment Checks + +Always wrap disabled auth in environment checks: + +```csharp +if (builder.Environment.IsDevelopment()) +{ + // Disable auth only in development +} + +if (builder.Environment.IsProduction()) +{ + // Always enable auth in production + throw new InvalidOperationException("Authentication cannot be disabled in production"); +} +``` + +## Re-Enabling Authentication for Production + +Before deploying to production, remove all authentication disabling code and enable proper security: + +```csharp +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +// ✅ Always enable authentication for production +builder.Services.AddElsa(elsa => +{ + elsa + .UseIdentity(identity => + { + identity.UseConfigurationBasedIdentityProvider(); + }) + .UseDefaultAuthentication() + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); + +var app = builder.Build(); + +// ✅ Always use authentication middleware +app.UseAuthentication(); +app.UseAuthorization(); + +app.UseWorkflowsApi(); +app.Run(); +``` + +For production authentication options, see: +- [Security & Authentication Guide](README.md) +- [External Identity Providers](external-identity-providers.md) + +## Troubleshooting + +### Studio Still Prompts for Login + +**Cause:** Studio authorization is still enabled. + +**Fix:** Ensure Studio is configured without authentication requirements: +```csharp +builder.Services.AddElsaStudio(studio => +{ + studio.ConfigureHttpClient(options => + { + options.BaseAddress = new Uri("https://localhost:5001"); + // No authentication configuration - allows anonymous access + }); +}); +``` + +Also verify that Elsa Server has disabled security (see Method 1 above). + +### API Returns 401 Unauthorized + +**Cause:** `UseAuthentication()` or `UseAuthorization()` middleware is still active, or `DisableSecurity()` wasn't called. + +**Fix:** Ensure you've disabled security before building the app: +```csharp +Elsa.Api.Common.Options.EndpointSecurityOptions.DisableSecurity(); +var app = builder.Build(); +``` + +### Cannot Access from Another Machine on Network + +**Cause:** Application is bound to localhost only. + +**Fix (Development Only):** Bind to all interfaces: +```csharp +builder.WebHost.UseUrls("http://*:5001"); +``` + +{% hint style="warning" %} +Only do this in isolated development networks. Never expose unauthenticated Elsa to the internet. +{% endhint %} + +## Next Steps + +- **Learn Elsa**: Explore [Getting Started](../../getting-started/hello-world.md) tutorials +- **Create Workflows**: Build your first workflow with [Elsa Studio](../../application-types/elsa-studio.md) +- **Enable Auth for Production**: Follow [Security & Authentication Guide](README.md) +- **Integrate Identity**: Set up [External Identity Providers](external-identity-providers.md) + +## Related Documentation + +- [Security & Authentication Guide](README.md) - Comprehensive security configuration +- [External Identity Providers](external-identity-providers.md) - Integrating with Azure AD, Auth0, etc. +- [Hosting Elsa in an Existing App](../onboarding/hosting-elsa-in-existing-app.md) - Integration guide +- [Blazor Dashboard Integration](../integration/blazor-dashboard.md) - Studio setup + +--- + +**Last Updated:** 2025-12-02 +**Addresses Issues:** #15 diff --git a/guides/security/external-identity-providers.md b/guides/security/external-identity-providers.md new file mode 100644 index 0000000..92dd751 --- /dev/null +++ b/guides/security/external-identity-providers.md @@ -0,0 +1,586 @@ +--- +description: >- + Guide to integrating Elsa Server with external identity providers including Microsoft Entra ID, Auth0, Keycloak, and other OpenID Connect / OAuth2 providers. +--- + +# External Identity Providers + +This guide covers integrating Elsa Server with external identity providers (IdP) for authentication and authorization. By integrating with an external IdP, you can leverage existing user directories, enable Single Sign-On (SSO), and centralize identity management across your organization. + +## Overview + +Elsa Server supports integration with any identity provider that implements standard authentication protocols: + +- **OpenID Connect (OIDC)**: Industry-standard authentication layer on top of OAuth 2.0 +- **OAuth 2.0**: Authorization framework for delegated access +- **SAML 2.0**: Enterprise SSO protocol (via OIDC bridge or direct integration) + +### Supported Identity Providers + +- **Microsoft Entra ID (Azure AD)**: Microsoft's cloud identity service +- **Auth0**: Cloud-based authentication and authorization platform +- **Keycloak**: Open-source identity and access management +- **Okta**: Cloud-based identity management +- **Google Identity**: Google Workspace and consumer accounts +- **OpenIddict**: Self-hosted OIDC server for .NET +- **IdentityServer**: .NET identity and access control framework +- **Any OIDC-compliant provider**: Generic integration pattern + +## Why Use External Identity Providers? + +**Benefits:** +- **Centralized user management**: Single source of truth for user identities +- **Single Sign-On (SSO)**: Users authenticate once across all applications +- **Multi-Factor Authentication (MFA)**: Enhanced security with 2FA/MFA +- **Audit and compliance**: Centralized authentication logs and policies +- **Reduced development**: Leverage existing identity infrastructure +- **Enterprise features**: Conditional access, risk-based authentication, identity governance + +**Use Cases:** +- **Enterprise deployments**: Integrate with corporate identity systems (Microsoft Entra ID, Okta) +- **Multi-tenant SaaS**: Per-tenant identity provider configuration +- **B2B integrations**: Allow partner organizations to use their own identity providers +- **Compliance requirements**: Meet security standards requiring MFA and audit trails + +## General Integration Pattern + +Regardless of the specific provider, the general pattern for integrating Elsa with an external IdP is: + +### 1. Register Elsa in the Identity Provider + +- Create an application registration in your IdP +- Configure redirect URIs for authentication callbacks +- Obtain client credentials (client ID, client secret) +- Configure token lifetimes and allowed scopes + +### 2. Configure ASP.NET Core Authentication + +- Install necessary NuGet packages +- Configure authentication middleware in `Program.cs` +- Map external claims to Elsa's authorization model +- Configure token validation parameters + +### 3. Configure Elsa to Use ASP.NET Core Authentication + +- Enable Elsa's default authentication +- Configure authorization policies +- Map roles and claims to Elsa permissions + +### 4. Configure Elsa Studio (if used) + +- Configure Studio to use the same IdP +- Set up authentication token forwarding from Studio to Elsa Server +- Configure OIDC client in Studio + +## High-Level Architecture + +``` +┌─────────────────┐ +│ End User │ +└────────┬────────┘ + │ + │ 1. Access Studio + v +┌─────────────────────────────────────────┐ +│ Elsa Studio (Blazor) │ +│ │ +│ 2. Redirect to IdP for authentication │ +└────────────┬────────────────────────────┘ + │ + │ 3. Authentication + v +┌─────────────────────────────────────────┐ +│ Identity Provider (Azure AD/Auth0) │ +│ │ +│ 4. Return token (access + ID token) │ +└────────────┬────────────────────────────┘ + │ + │ 5. Authenticated requests + v +┌─────────────────────────────────────────┐ +│ Elsa Server (API) │ +│ │ +│ 6. Validate token, authorize request │ +└─────────────┬───────────────────────────┘ + │ + │ 7. Execute workflow + v +┌─────────────────────────────────────────┐ +│ Database │ +└─────────────────────────────────────────┘ +``` + +## Provider-Specific Integration Guides + +### Microsoft Entra ID (Azure AD) + +Microsoft Entra ID (formerly Azure Active Directory) is Microsoft's cloud-based identity and access management service. + +{% hint style="info" %} +**Note:** Azure Active Directory was rebranded as Microsoft Entra ID in 2023. Both names refer to the same service. This guide uses "Microsoft Entra ID" but you may see "Azure AD" in older documentation and code examples. +{% endhint %} + +**Key Features:** +- Integration with Microsoft 365 and Azure services +- Conditional Access for advanced security policies +- Support for thousands of pre-integrated SaaS applications +- B2B and B2C capabilities + +**Integration Steps:** + +1. **Register application in Azure Portal** + - Navigate to Azure Active Directory → App registrations + - Create new registration for Elsa Server + - Configure redirect URIs (e.g., `https://elsa.example.com/signin-oidc`) + - Generate client secret + - Note Application (client) ID and Directory (tenant) ID + +2. **Configure API permissions** + - Add required Microsoft Graph permissions (if needed) + - Common: `User.Read`, `openid`, `profile`, `email` + +3. **Configure authentication in Elsa Server** + - Install package: `Microsoft.AspNetCore.Authentication.OpenIdConnect` + - Configure OIDC authentication middleware + - Map Azure AD roles/groups to Elsa authorization policies + +4. **Configure Elsa Studio** + - Configure Studio OIDC client + - Ensure same tenant and client ID + - Configure token forwarding to Elsa Server API + +**Configuration Example:** + +{% hint style="info" %} +**Note:** The following example shows the essential configuration structure. Replace placeholders (`{tenant-id}`, `{client-id}`, `{client-secret}`) with your actual Azure AD values. For production deployments, store secrets in environment variables or a secure key vault. +{% endhint %} + +```csharp +// Program.cs +builder.Services + .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + .AddOpenIdConnect(options => + { + options.Authority = "https://login.microsoftonline.com/{tenant-id}"; + options.ClientId = "{client-id}"; + options.ClientSecret = "{client-secret}"; + options.ResponseType = "code"; + options.SaveTokens = true; + + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("email"); + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = "https://login.microsoftonline.com/{tenant-id}/v2.0", + ValidateAudience = true, + ValidAudience = "{client-id}" + }; + }); + +builder.Services.AddElsa(elsa => +{ + elsa + .UseDefaultAuthentication() + .UseWorkflowManagement() + .UseWorkflowRuntime() + .UseWorkflowsApi(); +}); +``` + +**Further Reading:** +- [Microsoft Identity Platform Documentation](https://learn.microsoft.com/en-us/entra/identity-platform/) +- [ASP.NET Core with Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-web-app-aspnet-core-sign-in) + +### Auth0 + +Auth0 is a cloud-based authentication and authorization platform with extensive features and integrations. + +**Key Features:** +- Support for social login (Google, Facebook, GitHub, etc.) +- Custom databases and passwordless authentication +- Extensive customization via rules and hooks +- Global CDN for fast authentication +- Built-in MFA support + +**Integration Steps:** + +1. **Create Auth0 application** + - Log into Auth0 Dashboard + - Create new Regular Web Application + - Configure Allowed Callback URLs + - Note Domain, Client ID, and Client Secret + +2. **Configure allowed callback URLs** + - Add `https://elsa.example.com/signin-oidc` + - Add `https://studio.example.com/signin-oidc` + +3. **Define API in Auth0** + - Create API for Elsa Server + - Define permissions/scopes (e.g., `workflows:read`, `workflows:write`) + - Configure token lifetime + +4. **Configure authentication in Elsa Server** + - Install package: `Microsoft.AspNetCore.Authentication.OpenIdConnect` + - Configure OIDC with Auth0 settings + - Validate access tokens using Auth0 audience + +**Configuration Example:** + +{% hint style="info" %} +Replace `{your-domain}` with your Auth0 domain (e.g., `mycompany.auth0.com`) and `{api-identifier}` with your API identifier from the Auth0 dashboard. +{% endhint %} + +```csharp +builder.Services + .AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.Authority = "https://{your-domain}.auth0.com/"; + options.Audience = "{api-identifier}"; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = "https://{your-domain}.auth0.com/", + ValidateAudience = true, + ValidAudience = "{api-identifier}", + ValidateLifetime = true + }; + }); + +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("WorkflowAdmin", policy => + policy.RequireClaim("permissions", "workflows:admin")); +}); +``` + +**Further Reading:** +- [Auth0 ASP.NET Core Integration](https://auth0.com/docs/quickstart/webapp/aspnet-core) +- [Auth0 APIs Documentation](https://auth0.com/docs/get-started/apis) + +### Keycloak + +Keycloak is an open-source identity and access management solution that can be self-hosted. + +**Key Features:** +- Self-hosted (full control over deployment) +- Support for LDAP/Active Directory integration +- User federation and identity brokering +- Fine-grained authorization services +- Protocol mappers for custom claims + +**Integration Steps:** + +1. **Create Keycloak realm and client** + - Log into Keycloak Admin Console + - Create new realm for Elsa + - Create confidential client for Elsa Server + - Configure redirect URIs + +2. **Configure client settings** + - Enable Standard Flow (authorization code flow) + - Set Access Type to confidential + - Note Client ID and Client Secret + +3. **Define roles and groups** + - Create roles: `workflow-admin`, `workflow-designer`, `workflow-viewer` + - Assign roles to users or groups + +4. **Configure authentication in Elsa Server** + - Install package: `Microsoft.AspNetCore.Authentication.OpenIdConnect` + - Configure OIDC with Keycloak endpoint + - Map Keycloak roles to ASP.NET Core claims + +**Configuration Example:** + +{% hint style="info" %} +Replace `{realm-name}`, `{client-id}`, and `{client-secret}` with values from your Keycloak configuration. The authority URL should point to your Keycloak instance and realm. +{% endhint %} + +```csharp +builder.Services + .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + .AddOpenIdConnect(options => + { + options.Authority = "https://keycloak.example.com/realms/{realm-name}"; + options.ClientId = "{client-id}"; + options.ClientSecret = "{client-secret}"; + options.ResponseType = "code"; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("roles"); + + options.ClaimActions.MapJsonKey("role", "roles"); + }); + +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("WorkflowAdmin", policy => + policy.RequireRole("workflow-admin")); +}); +``` + +**Further Reading:** +- [Keycloak Documentation](https://www.keycloak.org/documentation) +- [Securing ASP.NET Core with Keycloak](https://www.keycloak.org/docs/latest/securing_apps/) + +### Generic OIDC Provider + +For any other OIDC-compliant provider, follow this generic integration pattern. + +**Prerequisites:** +- Provider must support OpenID Connect Discovery +- Provider must issue JWT access tokens +- Provider must support authorization code flow + +**Configuration Steps:** + +1. **Obtain provider metadata** + - Authority URL (e.g., `https://idp.example.com`) + - Client ID and Client Secret + - Supported scopes + +2. **Configure authentication** + +```csharp +builder.Services + .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + .AddOpenIdConnect(options => + { + options.Authority = builder.Configuration["OIDC:Authority"]; + options.ClientId = builder.Configuration["OIDC:ClientId"]; + options.ClientSecret = builder.Configuration["OIDC:ClientSecret"]; + options.ResponseType = "code"; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + + // Add custom scopes + foreach (var scope in builder.Configuration.GetSection("OIDC:Scopes").Get() ?? Array.Empty()) + { + options.Scope.Add(scope); + } + }); +``` + +## Authorization and Claims Mapping + +After authentication, you need to map external claims to Elsa's authorization model. + +### Mapping Roles + +```csharp +builder.Services.AddAuthorization(options => +{ + // Map external roles to Elsa policies + options.AddPolicy("WorkflowAdmin", policy => + policy.RequireRole("WorkflowAdmin", "admin", "workflow_admin")); + + options.AddPolicy("WorkflowDesigner", policy => + policy.RequireRole("WorkflowDesigner", "designer", "workflow_designer")); + + options.AddPolicy("WorkflowViewer", policy => + policy.RequireRole("WorkflowViewer", "viewer", "workflow_viewer")); +}); +``` + +### Custom Claims + +```csharp +builder.Services + .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + .AddOpenIdConnect(options => + { + // ... other config ... + + options.Events = new OpenIdConnectEvents + { + OnTokenValidated = context => + { + var claimsIdentity = (ClaimsIdentity)context.Principal.Identity; + + // Add custom claims based on external claims + var permissions = context.Principal.FindAll("permissions"); + foreach (var permission in permissions) + { + claimsIdentity.AddClaim(new Claim("elsa_permission", permission.Value)); + } + + return Task.CompletedTask; + } + }; + }); +``` + +## Elsa Studio Configuration + +When using an external IdP, configure Elsa Studio to authenticate users and forward tokens to Elsa Server. + +**Studio Program.cs (Conceptual):** + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +// Configure authentication (same IdP as Server) +builder.Services + .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + .AddOpenIdConnect(options => + { + options.Authority = builder.Configuration["OIDC:Authority"]; + options.ClientId = builder.Configuration["OIDC:ClientId"]; + options.ClientSecret = builder.Configuration["OIDC:ClientSecret"]; + options.ResponseType = "code"; + options.SaveTokens = true; + }); + +// Configure Elsa Studio +builder.Services.AddElsaStudio(studio => +{ + studio.ConfigureHttpClient(options => + { + options.BaseAddress = new Uri("https://elsa-server.example.com"); + }); + + // Forward authentication token to Elsa Server + studio.ConfigureHttpClient((sp, client) => + { + var httpContextAccessor = sp.GetRequiredService(); + var accessToken = httpContextAccessor.HttpContext? + .GetTokenAsync("access_token") + .GetAwaiter() + .GetResult(); + + if (!string.IsNullOrEmpty(accessToken)) + { + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", accessToken); + } + }); +}); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); +app.MapBlazorHub() + .RequireAuthorization(); // Require authentication for Studio +app.MapFallbackToPage("/_Host"); + +app.Run(); +``` + +## REST API Integration + +When calling Elsa Server APIs from external applications, use bearer token authentication: + +```bash +# Obtain access token from IdP +ACCESS_TOKEN="eyJhbGc..." + +# Call Elsa API with token +curl https://elsa-server.example.com/elsa/api/workflow-definitions \ + -H "Authorization: Bearer $ACCESS_TOKEN" +``` + +For detailed REST API usage, see: +- [Running Workflows via REST](../running-workflows/README.md) +- [HTTP Workflows Guide](../http-workflows/README.md) + +## Security Best Practices + +When integrating with external IdPs: + +1. **Always use HTTPS**: Never transmit tokens over HTTP +2. **Validate tokens properly**: Check issuer, audience, expiration, and signature +3. **Use short-lived access tokens**: Configure appropriate token lifetimes (1 hour recommended) +4. **Implement refresh token rotation**: Enhance security with refresh token rotation +5. **Store secrets securely**: Use Azure Key Vault, AWS Secrets Manager, or similar +6. **Enable MFA**: Require multi-factor authentication for administrative access +7. **Audit authentication events**: Log all authentication and authorization decisions +8. **Implement RBAC**: Use role-based access control with least-privilege principle + +For comprehensive security guidance, see: +- [Security & Authentication Guide](README.md) + +## Troubleshooting + +### Common Issues + +#### Token Validation Fails + +**Symptoms:** 401 Unauthorized, "IDX10205: Issuer validation failed" + +**Solutions:** +- Verify Authority URL matches IdP issuer +- Check token expiration +- Ensure clock synchronization (NTP) +- Validate audience matches client ID + +#### Claims Not Mapped + +**Symptoms:** User authenticated but lacks required permissions + +**Solutions:** +- Check claim names in token (decode JWT at jwt.io) +- Verify claims mapping in OIDC options +- Ensure roles/permissions are assigned in IdP +- Review authorization policies + +#### CORS Errors + +**Symptoms:** Studio can't call Elsa Server API + +**Solutions:** +- Configure CORS on Elsa Server to allow Studio origin +- Ensure credentials are allowed (`AllowCredentials()`) +- Check preflight (OPTIONS) requests succeed + +For more troubleshooting guidance, see: +- [Troubleshooting Guide](../troubleshooting/README.md) +- [Blazor Dashboard Integration](../integration/blazor-dashboard.md) + +## Planned Sections (Future Updates) + +The following sections will be expanded in future documentation updates: + +- **Detailed Azure AD B2C Integration**: Consumer identity scenarios +- **Multi-Tenant IdP Configuration**: Per-tenant identity provider setup +- **Custom Authorization Handlers**: Implementing fine-grained permissions +- **Token Caching and Refresh**: Performance optimization strategies +- **Federated Identity**: Chain multiple identity providers +- **API Key + OIDC Hybrid**: Machine-to-machine with user authentication +- **Identity Provider Failover**: High availability patterns + +## Next Steps + +- **Configure your IdP**: Follow provider-specific documentation +- **Test authentication flow**: Verify token issuance and validation +- **Implement authorization**: Map roles and claims to Elsa policies +- **Deploy to production**: Follow [Security Guide](README.md) best practices +- **Monitor authentication**: Set up logging and alerting + +## Related Documentation + +- [Security & Authentication Guide](README.md) - Comprehensive security configuration +- [Disable Auth in Dev](disable-auth.md) - Development-only auth bypass +- [Blazor Dashboard Integration](../integration/blazor-dashboard.md) - Studio authentication +- [Hosting Elsa in an Existing App](../onboarding/hosting-elsa-in-existing-app.md) - Integration patterns +- [Kubernetes Deployment](../deployment/kubernetes.md) - Production deployment + +--- + +**Last Updated:** 2025-12-02 +**Addresses Issues:** #16 (partial) +**Status:** Foundational guide with provider-specific sections to be expanded based on community feedback and real-world integration patterns.