Implementing authorization in temporal server

Table of Contents

Introduction: Temporal Authentication Architecture

Temporal is a Durable Execution platform that allows developers to write resilient application code that survives system failures, network outages, and server crashes. By abstracting away the complexities of queues, databases, and state machines, Temporal enables you to build long-running, fault-tolerant workflows in familiar programming languages like Go, Java, and Python.

However, as you move from a local development environment to a production-ready infrastructure, security becomes paramount. A self-hosted Temporal cluster, by default, does not have fine-grained access control. This means that without a proper security layer, any user with network access to the server could potentially view workflow histories, terminate critical tasks, or even delete entire namespaces.

To solve this, Temporal provides a pluggable security architecture that integrates with standard Single Sign-On (SSO) providers using JSON Web Tokens (JWT). By leveraging an external Identity Provider (IdP) like Keycloak, you can enforce “who can do what” within your cluster through a combination of authentication (verifying identity) and authorization (controlling access).

This article provides a practical, step-by-step guide to securing a self-hosted Temporal cluster with Keycloak. We will cover how to configure a dedicated realm, set up client-level permissions, and integrate them directly into the Temporal Server and Web UI.

Temporal uses a Pluggable Authorization model based on JSON Web Tokens (JWT). In this flow, the Temporal Server acts as the Resource Server. It does not manage users itself, but instead validates tokens issued by an external Identity Provider (IdP) like Keycloak.

How it Works

  1. Authentication: A user or worker authenticates with Keycloak and receives a JWT.
  2. Authorization: When the user makes a request to Temporal (via CLI, UI, or SDK), they present this token.
  3. Validation: The Temporal Server verifies the token’s signature using the public keys from Keycloak.
  4. Enforcement: Temporal checks the custom permissions claim within the token to decide if the action is allowed.

The Required Payload

Temporal’s default authorizer expects a flat array of strings in the permissions claim. This is why the Keycloak Protocol Mapper (Step 4 in Keycloak configuration) is critical. Your token payload should look like this:

{
  "sub": "dd6d098e-c9d6...",
  "permissions": [
    "temporal-system:admin",
    "default:read",
    "namespace1:write"
  ]
}

Understanding Permission Scopes

Permissions are defined using the format target:action:

  • Targets:
    • temporal-system: Applies to the entire Temporal cluster (Global).
    • namespace: Applies only to a specific Namespace (e.g., default, namespace1).
  • Actions:
    • admin: Full access to everything within the target. Provides the ability to manage settings and state.
    • write: Permission to start/signal workflows and modify data.
    • read: View-only access to workflow history and cluster state.

temporal-system:admin is the most powerful scope; it overrides all other namespace restrictions and grants full administrative control over the entire cluster.

Configuration for Keycloak

First, you need to run the Keycloak server locally. In this article, I will show the configuration for Keycloak in version 18.0. You can use the following Docker Compose configuration:

services:
  keycloak:
    container_name: keycloak
    image: keycloak/keycloak:18.0.0
    command: start-dev --http-port 20080
    environment:
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_ADMIN_PASSWORD=admin
    ports:
      - 20080:20080

And you can log in to Keycloak by going to:
http://localhost:20080/

Click Administration Console and log in using the user and password: admin/admin

1. Realm Setup

Create a dedicated realm to isolate your application from the system default master realm.

  • Create Realm: Click the realm dropdown (top left) and select Add Realm. Name it temporal.
  • Token Lifespan: Go to the Realm Settings -> Tokens tab. Set Access Token Lifespan to 12 Hours for testing. Ensure SSO Session Max is also set to at least 12 Hours so the session remains active during tests.

2. Client Configuration

Set up the client to serve as the gateway for your application.

  1. Create Client: Go to Clients -> Create.
    • Client ID: temporal-access.
    • Client Protocol: openid-connect.
    • Click Save.
  2. Settings Configuration:
    • Access Type: Change to confidential.
    • Direct Access Grants Enabled: Set to ON (Required for REST API testing via password grant).
    • Valid Redirect URIs: Enter http://localhost:8089/* to match your Temporal UI.
    • Click Save at the bottom of the page.
  3. Get Client Secret: Click the Credentials tab and copy the Secret for your API calls.

3. Cleaning the JWT (Removing Default Roles)

To remove the standard realm_access and resource_access claims and roles like offline_access and uma_authorization, you must modify the client’s scopes.

  1. Remove “roles” Scope:
    • Inside the temporal-access client, navigate to the Client Scopes tab.
    • In the Assigned Default Client Scopes table, select the row named roles and click Remove selected.

4. Custom Role and Permission Mapping

Configure client-level roles to appear as a flat permissions array in your JWT.

Step A: Create Client Roles

  1. Inside your temporal-access client, click the Roles tab.
  2. Click Add Role and create your permissions (e.g., temporal-system:admin).

Step B: Create the Protocol Mapper

  1. Go to the Mappers tab of the temporal-access client.
  2. Click Create.
    • Mapper Type: Select User Client Role.
    • Name: permissions-mapper.
    • Client ID: temporal-access.
    • Token Claim Name: permissions.
    • Claim JSON Type: String.
    • Multivalued: ON (Ensures the claim is a JSON array []).
    • Add to access token: ON.
  3. Click Save.

5. User configuration

  1. Add User: Go to Users -> Add User.
    • Name: temporal-user.
    • Click Save.
  2. Set Credentials:
    • Click the Credentials tab.
    • Set a password and turn Temporary to OFF.
  3. Role Mapping:
    • Click the Role Mappings tab.
    • In the Client Roles dropdown, select temporal-access.
    • Select your permissions (e.g., temporal-system:admin) and click Add selected.

6. Token Generation via REST API

Send a POST request to obtain your token:

  • URL: http://localhost:20080/realms/temporal/protocol/openid-connect/token
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    • grant_type: password
    • client_id: temporal-access
    • client_secret: [YOUR_SECRET]
    • username: temporal-user
    • password: [USER-PASSWORD]

Configuration for Temporal

For the temporal server container, you need to define the following environment variables:

- TEMPORAL_AUTH_ENABLED=true
- TEMPORAL_JWT_KEY_SOURCE1=http://keycloak:20080/realms/temporal/protocol/openid-connect/certs
- TEMPORAL_AUTH_CLAIM_MAPPER=default
- USE_INTERNAL_FRONTEND=1
- SERVICES=frontend:history:matching:worker:internal-frontend

And for the temporal-ui container:

- TEMPORAL_AUTH_ENABLED=true
- TEMPORAL_AUTH_PROVIDER_URL=http://keycloak:20080/realms/temporal
- TEMPORAL_AUTH_CLIENT_ID=temporal-access
- TEMPORAL_AUTH_CLIENT_SECRET=<YOUR-CLIENT-SECRET>
- TEMPORAL_AUTH_SCOPES=openid,profile,email

Also, you need to add to your /etc/hosts file the following entry:

127.0.0.1 keycloak

It’s needed because the address defined in the variable TEMPORAL_AUTH_PROVIDER_URL is used for user redirection during SSO authentication, so the “keycloak” DNS hostname needs to be resolved on your local machine.

Verification Example

Once your containers are running and Keycloak is configured, try to list the namespaces without an authorization token.

  1. Attempt Access Without a Token If you have tctl installed locally (or via the admin-tools container), set your server address and attempt to list namespaces:
# Set the server address
export TEMPORAL_ADDRESS="localhost:7233"

# Attempt to list namespaces
tctl namespace list

Expected Result: The server should reject the request because no identity was provided.

Error: Error when list namespaces info
Error Details: rpc error: code = PermissionDenied desc = Request unauthorized.
  1. Attempt Access With a Valid Token Now. Use the token you generated via the REST API in Step 6. Export it as an environment variable that the Temporal CLI uses for gRPC metadata:
# Export your actual token from the Keycloak REST response
export TEMPORAL_GRPC_META_AUTHORIZATION="Bearer eyJhbGciOiJSUzI1NiIsInR5cCIg..."

# Execute the command again
tctl namespace list

Expected Result: If the user assigned to this token has the temporal-system:admin permission the command will now succeed:

Name: default
Id: 2d3d4e5f-6a7b-8c9d-0e1f-2a3b4c5d6e7f
Description: Default namespace for Temporal
OwnerEmail: 
Status: REGISTERED
...

Scroll to Top