How to configure Camunda 8.8.0 User Flow with External Identity Provider

Hi, I want to use Camunda 8.8.0 to work with only an External Identity Provider that supports OpenID Connect, like:

Duende Identity Server

And I want to be able to use User Flow, so that users can log in to Camunda using External Identity Provider credentials, and after login, they can Launch/Retrieve/Claim/Complete Tasks using their External Identity Provider credentials (Access Token).

After that, I want to be able to see the process instance history and all the actions performed by the External Identity Provider user.

Do all the above requirements exist in Camunda 8.8.0 APIs and not on the Camunda tasklist?
And where is the best place to start reading about the configuration?

Hi! Great question about configuring Camunda 8.8.0 with an external identity provider. Yes, all your requirements are supported in Camunda 8.8.0! Let me break this down for you:

:white_check_mark: Your Requirements - All Supported

1. External Identity Provider Integration

  • Camunda 8.8.0 fully supports OpenID Connect (OIDC) providers
  • Duende Identity Server will work perfectly as it’s OIDC-compliant
  • Users can authenticate using their external IdP credentials

2. User Task Flow

  • Users can log in via external IdP → access Camunda → claim/complete tasks
  • Full task lifecycle supported: Launch/Retrieve/Claim/Complete
  • All actions are performed using external IdP user identity

3. Audit Trail & History

  • Complete process instance history is maintained
  • All user actions (claim, complete, etc.) are recorded
  • Audit trail shows which external IdP user performed each action

:rocket: How It Works

Authentication Flow:

  1. User accesses Camunda UI → Redirected to Duende Identity Server
  2. User logs in with external credentials → OIDC tokens issued
  3. User redirected back to Camunda with authentication tokens
  4. Camunda uses tokens for session management and authorization

Task Operations:

  • Users see tasks filtered by their roles/groups from OIDC claims
  • Tasks must be claimed before completion (via API or UI)
  • All actions are recorded in process instance history

:books: Best Places to Start Reading

Primary Configuration Guide:

Key Configuration Steps:

  1. Register Applications in Duende Identity Server for each Camunda component
  2. Configure Environment Variables for OIDC connection:
    CAMUNDA_IDENTITY_TYPE=GENERIC
    CAMUNDA_IDENTITY_ISSUER=<YOUR_DUENDE_URL>
    CAMUNDA_IDENTITY_CLIENT_ID=<CLIENT_ID>
    CAMUNDA_IDENTITY_CLIENT_SECRET=<CLIENT_SECRET>
    SPRING_PROFILES_ACTIVE=oidc
    

Additional Resources:

:dart: Next Steps

  1. Start with the OIDC provider configuration guide above
  2. Register your Camunda applications in Duende Identity Server
  3. Configure the environment variables for each Camunda component
  4. Test the authentication flow

The documentation provides generic OIDC configuration steps that apply to Duende Identity Server. Since Duende is OIDC-compliant, you’ll follow the standard configuration process.

Would you like me to help you with any specific part of the configuration process?

@zee-bot
I want the above requirements to be on Camunda APIs not on the Camunda TaskList

Perfect! Thanks for the clarification. Yes, all your requirements are fully supported via Camunda APIs in 8.8.0! Here’s how to achieve your user flow using APIs with external identity providers:

:white_check_mark: API-Based User Flow with External Identity Provider

1. Authentication Flow for APIs

Step 1: Register API Client in Duende Identity Server

  • Register a client application for API access
  • Configure appropriate scopes and audiences for Camunda components

Step 2: Obtain Access Token

curl --location --request POST '<DUENDE_TOKEN_ENDPOINT>' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id=<CLIENT_ID>' \
  --data-urlencode 'client_secret=<CLIENT_SECRET>' \
  --data-urlencode 'audience=<AUDIENCE>' \
  --data-urlencode 'grant_type=client_credentials'

2. User Task Operations via API

Claim a Task:

curl -X POST 'http://<CAMUNDA_HOST>/user-tasks/<userTaskKey>/assignment' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{ "action": "claim", "assignee": "<userId>" }'

Complete a Task:

curl -X POST 'http://<CAMUNDA_HOST>/user-tasks/<userTaskKey>/completion' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{ "variables": { "outcome": "approved" } }'

Retrieve Tasks (GraphQL):

curl -X POST 'http://<CAMUNDA_HOST>/graphql' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{ "query": "{ tasks { id name assignee state } }" }'

3. Complete User Flow

  1. User authenticates → Your app obtains OIDC access token from Duende
  2. Launch Process → Use token to call Zeebe API to start process instances
  3. Retrieve Tasks → Use token to query Tasklist API for available tasks
  4. Claim Tasks → Use token to assign tasks to external IdP users
  5. Complete Tasks → Use token to complete tasks with user data
  6. View History → Use token to query process instance history

4. Audit Trail & History via APIs

All actions performed via APIs are fully audited:

  • Process Instance History → Available via Operate API
  • User Task Events → Recorded with external IdP user identity
  • Audit Logging → All API operations logged with user context

5. Supported Authentication Flows

  • Client Credentials Grant → For service-to-service API calls
  • Authorization Code Flow → For user-based API access
  • Both REST and GraphQL APIs → Same Bearer token authentication

:books: Key Documentation for API Integration

Authentication:

OIDC Configuration:

API Operations:

:dart: Implementation Steps

  1. Configure OIDC → Set up Duende Identity Server integration
  2. Register API Clients → Create applications for each Camunda component
  3. Implement Token Flow → Build token acquisition in your application
  4. Use Bearer Tokens → Include access tokens in all API requests
  5. Test End-to-End → Verify claim/complete flow with external users

The access token from your Duende Identity Server will work seamlessly with all Camunda APIs (REST, GraphQL, Zeebe gRPC) for the complete user task lifecycle!

Would you like me to elaborate on any specific part of the API integration?

@zee-bot your latest answer doesn’t explain the configuration needed to be done in Camunda & Duende Identity Server to make them integrated.

You’re absolutely right! Let me provide the detailed configuration steps for both Camunda and Duende Identity Server integration:

:wrench: Duende Identity Server Configuration

1. Register Clients in Duende Identity Server

You need to register separate clients for each Camunda component. Here’s what you need to configure:

For each client, set:

  • Client ID: Unique identifier for each component
  • Client Secret: Secure secret for authentication
  • Grant Types: authorization_code, client_credentials
  • Scopes: openid, profile, email (and any custom scopes)
  • Redirect URIs: Component-specific URIs (see below)

Required Redirect URIs:

Identity:    https://<IDENTITY_URL>/auth/login-callback
Operate:     https://<OPERATE_URL>/identity-callback  
Tasklist:    https://<TASKLIST_URL>/identity-callback
Optimize:    https://<OPTIMIZE_URL>/api/authentication/callback
Web Modeler: https://<WEB_MODELER_URL>/login-callback
Console:     https://<CONSOLE_URL>

Example Duende Client Configuration:

new Client
{
    ClientId = "camunda-tasklist",
    ClientSecrets = { new Secret("your-secret".Sha256()) },
    AllowedGrantTypes = GrantTypes.Code,
    RedirectUris = { "https://tasklist.yourdomain.com/identity-callback" },
    AllowedScopes = { "openid", "profile", "email" },
    AllowOfflineAccess = true
}

:gear: Camunda 8.8.0 Configuration

Option 1: Helm Chart Configuration (values.yaml)

global:
  identity:
    auth:
      issuer: "https://your-duende-server.com"
      issuerBackendUrl: "https://your-duende-server.com"
      tokenUrl: "https://your-duende-server.com/connect/token"
      jwksUrl: "https://your-duende-server.com/.well-known/openid_configuration/jwks"
      type: "GENERIC"
      
      identity:
        clientId: "camunda-identity"
        existingSecret: "identity-client-secret"
        audience: "camunda-identity"
        initialClaimName: "sub"  # or "oid" depending on your setup
        initialClaimValue: "admin-user-id"
        
      operate:
        clientId: "camunda-operate"
        audience: "camunda-operate"
        existingSecret: "operate-client-secret"
        
      tasklist:
        clientId: "camunda-tasklist"
        audience: "camunda-tasklist"
        existingSecret: "tasklist-client-secret"
        
      optimize:
        clientId: "camunda-optimize"
        audience: "camunda-optimize"
        existingSecret: "optimize-client-secret"
        
      zeebe:
        clientId: "camunda-zeebe"
        audience: "camunda-zeebe"
        existingSecret: "zeebe-client-secret"
        
      webModeler:
        clientId: "camunda-web-modeler"
        clientApiAudience: "camunda-web-modeler-api"
        publicApiAudience: "camunda-web-modeler-public"
        
      console:
        clientId: "camunda-console"
        audience: "camunda-console"

Option 2: Docker Compose Configuration

For each Camunda service, add these environment variables:

# Identity Service
identity:
  environment:
    - CAMUNDA_IDENTITY_TYPE=GENERIC
    - CAMUNDA_IDENTITY_BASE_URL=http://identity:8080
    - CAMUNDA_IDENTITY_ISSUER=https://your-duende-server.com
    - CAMUNDA_IDENTITY_ISSUER_BACKEND_URL=https://your-duende-server.com
    - CAMUNDA_IDENTITY_CLIENT_ID=camunda-identity
    - CAMUNDA_IDENTITY_CLIENT_SECRET=your-identity-secret
    - CAMUNDA_IDENTITY_AUDIENCE=camunda-identity
    - IDENTITY_INITIAL_CLAIM_NAME=sub
    - IDENTITY_INITIAL_CLAIM_VALUE=admin-user-id
    - SPRING_PROFILES_ACTIVE=oidc

# Tasklist Service  
tasklist:
  environment:
    - CAMUNDA_IDENTITY_TYPE=GENERIC
    - CAMUNDA_IDENTITY_BASE_URL=http://identity:8080
    - CAMUNDA_IDENTITY_ISSUER=https://your-duende-server.com
    - CAMUNDA_IDENTITY_ISSUER_BACKEND_URL=https://your-duende-server.com
    - CAMUNDA_IDENTITY_CLIENT_ID=camunda-tasklist
    - CAMUNDA_IDENTITY_CLIENT_SECRET=your-tasklist-secret
    - CAMUNDA_IDENTITY_AUDIENCE=camunda-tasklist
    - SPRING_PROFILES_ACTIVE=oidc

# Operate Service
operate:
  environment:
    - CAMUNDA_IDENTITY_TYPE=GENERIC
    - CAMUNDA_IDENTITY_BASE_URL=http://identity:8080
    - CAMUNDA_IDENTITY_ISSUER=https://your-duende-server.com
    - CAMUNDA_IDENTITY_ISSUER_BACKEND_URL=https://your-duende-server.com
    - CAMUNDA_IDENTITY_CLIENT_ID=camunda-operate
    - CAMUNDA_IDENTITY_CLIENT_SECRET=your-operate-secret
    - CAMUNDA_IDENTITY_AUDIENCE=camunda-operate
    - SPRING_PROFILES_ACTIVE=oidc

:key: Key Configuration Points

Duende Identity Server Setup:

  1. Discovery Endpoint: Ensure /.well-known/openid-configuration is accessible
  2. CORS Configuration: Allow Camunda domains in CORS policy
  3. Claims Configuration: Map user claims (sub, email, groups) properly
  4. Scopes: Define appropriate scopes for each Camunda component

Camunda Configuration:

  1. Use GENERIC type for Duende Identity Server
  2. Set correct issuer URLs (both frontend and backend)
  3. Configure unique client IDs for each component
  4. Set proper audiences matching your Duende configuration
  5. Use Kubernetes secrets for client secrets in production

:rocket: Complete Integration Steps

  1. Configure Duende Identity Server with the clients above
  2. Deploy Camunda with the OIDC configuration
  3. Test authentication flow: User → Duende → Camunda
  4. Verify API access with Bearer tokens from Duende
  5. Test task operations via APIs with external user credentials

:books: Reference Documentation

This configuration will enable your complete user flow: external users authenticate via Duende → access Camunda APIs → perform task operations → full audit trail maintained!

Would you like me to elaborate on any specific part of this configuration?

@zee-bot , can the latest configuration be applied on c8run, if so please provide it ?

Great question! Yes, the OIDC configuration can be applied to c8run in Camunda 8.8.0! This is a significant improvement from previous versions.

:tada: c8run + OIDC Support in Camunda 8.8.0

Key Changes:

  • Before 8.8: c8run did NOT support Identity/OIDC integration
  • From 8.8 alpha: c8run now includes built-in Identity with OIDC support
  • No Docker required: You can now use external identity providers directly with c8run

:gear: c8run OIDC Configuration

Method 1: Environment Variables

Set these environment variables before starting c8run:

# Export environment variables
export CAMUNDA_IDENTITY_TYPE=GENERIC
export CAMUNDA_IDENTITY_BASE_URL=http://localhost:8080
export CAMUNDA_IDENTITY_ISSUER=https://your-duende-server.com
export CAMUNDA_IDENTITY_ISSUER_BACKEND_URL=https://your-duende-server.com
export CAMUNDA_IDENTITY_CLIENT_ID=camunda-c8run
export CAMUNDA_IDENTITY_CLIENT_SECRET=your-client-secret
export CAMUNDA_IDENTITY_AUDIENCE=camunda-c8run
export IDENTITY_INITIAL_CLAIM_NAME=sub
export IDENTITY_INITIAL_CLAIM_VALUE=admin-user-id
export SPRING_PROFILES_ACTIVE=oidc

# Then start c8run
./c8run start

Method 2: Configuration File (application.yaml)

Create an application.yaml file in your c8run directory:

camunda:
  identity:
    type: GENERIC
    baseUrl: http://localhost:8080
    issuer: https://your-duende-server.com
    issuerBackendUrl: https://your-duende-server.com
    clientId: camunda-c8run
    clientSecret: your-client-secret
    audience: camunda-c8run
    initialClaimName: sub
    initialClaimValue: admin-user-id

spring:
  profiles:
    active: oidc

:wrench: Duende Identity Server Setup for c8run

Register a single client in Duende for c8run:

new Client
{
    ClientId = "camunda-c8run",
    ClientSecrets = { new Secret("your-client-secret".Sha256()) },
    AllowedGrantTypes = GrantTypes.Code,
    RedirectUris = { 
        "http://localhost:8080/auth/login-callback",      // Identity
        "http://localhost:8081/identity-callback",        // Tasklist  
        "http://localhost:8082/identity-callback",        // Operate
        "http://localhost:8083/api/authentication/callback" // Optimize
    },
    AllowedScopes = { "openid", "profile", "email" },
    AllowOfflineAccess = true,
    AllowedCorsOrigins = { 
        "http://localhost:8080", 
        "http://localhost:8081", 
        "http://localhost:8082",
        "http://localhost:8083"
    }
}

:rocket: Complete c8run Setup Steps

1. Configure Duende Identity Server

  • Register the c8run client with appropriate redirect URIs
  • Configure CORS for localhost ports
  • Set up proper scopes and claims

2. Configure c8run

  • Set environment variables OR create application.yaml
  • Ensure all URLs point to your Duende server
  • Use consistent client ID and secret

3. Start c8run

# With environment variables
./c8run start

# Or specify config file
./c8run start --spring.config.location=application.yaml

4. Test the Integration

  • Access Tasklist: http://localhost:8081
  • You’ll be redirected to Duende for authentication
  • After login, you’ll be redirected back to Camunda
  • Use APIs with Bearer tokens from Duende

:warning: Important Notes

Limitations:

  • Alpha Feature: The new Identity in 8.8.0 is still in alpha
  • Local Development: Primarily designed for development/evaluation
  • Single Client: c8run typically uses one client for all components

Advantages:

  • No Docker Required: Direct OIDC integration without containers
  • Simplified Setup: Built-in Identity eliminates Keycloak dependency
  • Full API Support: All task operations work with external IdP tokens

:books: References

This setup gives you the complete user flow you wanted: Duende authentication → c8run APIs → task operations → audit trail - all in a local development environment!

Would you like me to help you troubleshoot any specific part of the c8run OIDC setup?

@devmsaleh - one point of clarification here: the APIs do not, by default, support the same user restrictions that the frontend applications support. For instance, Candidate Users on a user task are enforced by Tasklist, not the API, so you could query user tasks via the API that the user might not have access to in Tasklist. The reason for this is to allow you to build your own frontend task application with any customizations and rules that you want to build into it.

@devmsaleh - Excellent clarification! This is a crucial point for your API-based user flow. Let me explain the important distinction:

:warning: Critical API vs UI Behavior Difference

User Task Access Control:

Tasklist UI (Frontend):

  • :white_check_mark: Enforces Candidate Users/Groups automatically
  • :white_check_mark: Users only see tasks they’re authorized for
  • :white_check_mark: Built-in access control based on BPMN definitions

APIs (REST/GraphQL):

  • :x: Do NOT enforce Candidate Users/Groups by default
  • :x: Users can query/access tasks they shouldn’t see in Tasklist
  • :x: You must implement access control yourself

:wrench: What This Means for Your Implementation

Since you want to use APIs only (not Tasklist UI), you need to:

1. Implement Your Own Access Control

Query Tasks with Filtering:

# You need to filter tasks based on user identity
curl -X POST 'http://<CAMUNDA_HOST>/graphql' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "{ 
      tasks(query: { 
        candidateUser: \"current-user-id\",
        candidateGroup: [\"user-group-1\", \"user-group-2\"]
      }) { 
        id name assignee state 
      } 
    }"
  }'

2. Extract User Identity from OIDC Token

From your Duende Identity Server token:

  • Extract user ID from sub claim
  • Extract groups/roles from custom claims
  • Use these for task filtering

3. Validate Task Access Before Operations

Before claiming/completing tasks:

// Pseudo-code for access validation
async function claimTask(taskId, userId, userGroups) {
  // 1. Get task details
  const task = await getTaskById(taskId);
  
  // 2. Check if user has access
  const hasAccess = task.candidateUsers.includes(userId) || 
                   task.candidateGroups.some(group => userGroups.includes(group));
  
  if (!hasAccess) {
    throw new Error('User not authorized for this task');
  }
  
  // 3. Proceed with claim operation
  return await claimTaskAPI(taskId, userId);
}

:dart: Recommended Implementation Pattern

1. Build Your Own Task Service Layer

class TaskService {
  constructor(camundaAPI, userContext) {
    this.api = camundaAPI;
    this.user = userContext; // From OIDC token
  }
  
  async getMyTasks() {
    // Filter by candidate users/groups
    return this.api.getTasks({
      candidateUser: this.user.id,
      candidateGroups: this.user.groups
    });
  }
  
  async claimTask(taskId) {
    // Validate access before claiming
    await this.validateTaskAccess(taskId);
    return this.api.claimTask(taskId, this.user.id);
  }
}

2. Leverage OIDC Claims for Authorization

  • Map Duende Identity Server groups to Camunda candidate groups
  • Use consistent user IDs between systems
  • Implement role-based access control

:white_check_mark: Benefits of This Approach

Why Camunda designed it this way:

  • Flexibility: Build custom task applications with your own rules
  • Customization: Implement business-specific access logic
  • Integration: Easier to integrate with existing authorization systems

For your use case:

  • You can implement stricter access control than Tasklist
  • You can add custom business rules for task visibility
  • You can integrate with Duende’s authorization features

:clipboard: Updated Implementation Checklist

  1. :white_check_mark: Configure Duende + Camunda OIDC integration
  2. :white_check_mark: Set up c8run with OIDC (as shown earlier)
  3. :warning: NEW: Implement user/group extraction from OIDC tokens
  4. :warning: NEW: Build task filtering logic based on candidate users/groups
  5. :warning: NEW: Add access validation before claim/complete operations
  6. :white_check_mark: Use Bearer tokens for all API calls
  7. :white_check_mark: Maintain audit trail (still works as expected)

This gives you full control over task access while maintaining the security and audit capabilities you need!

Does this change your implementation approach, or do you need help with the access control logic?

@zee-bot
Can you please provide the configuration for Camunda c8run 8.8 integration with the external identity provider Keycloak both configuration from c8run side and external keycloak side