Files
member-console/Keycloak-setup.md

5.6 KiB
Raw Permalink Blame History

Temporal Authorization with Keycloak Using a Shared Client Scope

This document describes a pattern for integrating Temporal authorization with Keycloak using Temporals default JWT claims mapper and authorizer.

Note: You do not strictly need separate OAuth2 clients for “web login” vs “Temporal worker”. A single Keycloak client can support both Authorization Code (browser login) and Client Credentials (service account) flows. Using separate clients is still recommended for least privilege, but reusing a single client is acceptable as long as role assignment and claim mapping are done carefully.

The goal is to:

  • Avoid polluting realm roles with Temporal-specific permissions
  • Avoid duplicating client roles across multiple Temporal-using clients
  • Support human users, service accounts, and multiple clients
  • Emit the exact permissions claim that Temporal expects, without custom code

Background: What Temporal Expects

Temporals default authorizer looks for a JWT claim (by default named permissions) containing multiple string values in the format:

<namespace>:<permission>

Examples:

  • default:read
  • payments:write
  • analytics:worker
  • system:admin

Temporal does not understand Keycloak roles, groups, or scopes directly — it only evaluates this claim.

Therefore, the entire Keycloak setup is about reliably producing a permissions[] claim in access tokens.


Design Overview

We use three Keycloak concepts together:

  1. A dedicated “authorization-only” client to own Temporal roles
  2. A reusable Client Scope to emit the permissions claim
  3. Role assignments on users, groups, and service accounts

This design works whether you:

  • use separate Keycloak clients for your web UI vs worker, or
  • reuse the same Keycloak client for both (by enabling service accounts on it)

Why this design?

Problem Why this solves it
Realm roles feel cluttered Temporal roles live in one obvious place
Multiple Temporal clients Roles are shared, not duplicated
Service accounts Work naturally via service-account users
Temporal defaults No custom authorizer or claim mapper required

Step 1: Create a Temporal Authorization Client

Create a new Keycloak client whose only purpose is to hold Temporal permission roles.

Example:

  • Client ID: temporal-authz
  • Access Type: Confidential (details dont matter much)
  • No login flows needed

Create Client Roles on temporal-authz

Create client roles whose names exactly match Temporals expected format:

default:read
default:write
default:worker
default:admin
payments:worker
payments:admin

Tip: You may use composite roles (e.g. namespace:admin composed of read/write/worker) to reduce assignment complexity.

This client is now the single source of truth for Temporal permissions.


Step 2: Assign Temporal Roles

Human Users

  • Prefer assigning roles via groups
  • Groups can be named however you like (temporal-default-writers, etc.)
  • Groups receive temporal-authz client roles
  • Users inherit them automatically

Service Accounts (client_credentials)

For each client that uses service accounts:

  1. Enable Service Accounts
  2. Go to Service Account Roles
  3. Assign roles from temporal-authz

Keycloak will treat the service account as a user, and role mapping works the same way.

Reusing a single client for web login + service account

If you want to reuse the same Keycloak client for both:

  • web login (Authorization Code + PKCE) and
  • the Temporal worker (Client Credentials / service account)

the critical rule is: assign Temporal worker permissions to the service account user, not to regular users.

This way:

  • the service account token includes permissions: ["default:worker", ...] (Temporal can poll task queues), but
  • user tokens do not accidentally include default:worker (reduces the impact of a leaked user token).

Step 3: Create a Client Scope to Emit Permissions

Create a Client Scope:

  • Name: temporal-permissions
  • Type: Optional
  • Display on consent screen: Off
  • Include in token scope: Off

This scope is responsible for producing the permissions claim.


Step 4: Add a Protocol Mapper

Inside the temporal-permissions client scope, add a mapper:

Mapper Configuration

  • Mapper Type: User Client Role (or similar, depending on Keycloak version)
  • Client ID: temporal-authz
  • Token Claim Name: permissions
  • Claim JSON Type: String
  • Multivalued: On
  • Add to access token: On
  • Add to ID token: Off (usually)
  • Add to userinfo: Off (usually)

This mapper:

  • Reads roles assigned from the temporal-authz client
  • Emits them as a multi-valued permissions claim

Step 5: Attach the Client Scope

For every client that should be allowed to call Temporal:

  1. Go to Clients → (client) → Client scopes
  2. Add temporal-permissions as a Default Client Scope

Now:

  • Any token issued for that client
  • From a user login or service account
  • Will include the permissions[] claim (if roles are assigned)

Resulting Token Example

{
  "sub": "…",
  "iss": "https://keycloak.example/realms/myrealm",
  "aud": "temporal-client",
  "permissions": [
    "default:worker",
    "default:write"
  ]
}

Temporals default authorizer can consume this without any customization.