Skip to main content

Connection and Encryption in the TypeScript SDK

Temporal Workers and Clients connect with your Temporal Cluster via gRPC, and must be configured securely for production. There are three main features to know:

  • Namespaces help isolate code from each other
  • TLS Encryption helps encrypt code in transit
  • Data Converter helps encrypt code at rest (available soon)

Temporal Server internally has other Security features, particularly Authorization.

An important part of Temporal's security model is that Temporal Server only manages state and time - it never actually sees or runs your Workflow/Activity code. Code is hosted by Temporal Workers that you run, and Temporal Server only sees inbound/outbound gRPC messages. This eliminates a whole class of problems particularly when providing Temporal to multiple teams in your company, or when working with Temporal Cloud as a customer.

Namespaces

A Namespace is the fundamental unit of isolation within Temporal, which is backed by a multi-tenant service.

You can use Namespaces to match the development lifecycle; for example, having separate dev and prod Namespaces. Or you could use them to ensure workflows between different teams never communicate; such as ensuring that the teamA Namespace never impacts the teamB Namespace.

  • By default, Temporal server has one "default" and one internal Namespace. All APIs and tools, such as the UI and CLI, default to the "default" Namespace if it is not specified. So, if you are not planning to use multiple Namespaces, we recommend using the default one.
  • Membership: Task Queue names and Workflow Ids must all correspond to a specific Namespace. For example, when a Workflow is started, it starts within a specific Namespace.
  • Uniqueness: Temporal guarantees a unique Workflow Id within a Namespace. Temporal supports running Workflow Executions that use the same Workflow Id if they are in different Namespaces.
  • Namespace Configuration: Various configuration options like the retention period or Archival destination are configured per Namespace through a special CRUD API or through tctl.

All SDK connections (whether Workers or Clients) are to a specific namespace. If not specified in WorkflowClientOptions, this defaults to the default namespace.

const connection = new Connection();

const client = new WorkflowClient(connection.service, {
namespace: 'my-namespace-name', // defaults to 'default'
});

Encryption in transit with mTLS

There are two classes in the SDK that connect to the Temporal server, the Worker and the client Connection. When instantiating either of them, you may choose whether to connect securely or not.

A full example for Clients looks like this:

import { Connection, WorkflowClient } from '@temporalio/client';

const connection = new Connection({
address: 'foo.bar.tmprl.cloud', // as provisioned
tls: {
// See docs for other TLS options
clientCertPair: {
crt: clientCert,
key: clientKey,
},
},
});
await connection.untilReady();
const client = new WorkflowClient(connection.service, { namespace: 'foo.bar' }); // as explained above

A full example for Workers looks like this:

import { Worker, Core } from '@temporalio/worker';
await Core.install({
serverOptions: {
address: 'foo.bar.tmprl.cloud', // as provisioned
namespace: 'foo.bar', // as provisioned
tls: {
// See docs for other TLS options
clientCertPair: {
crt: clientCert,
key: clientKey,
},
},
},
});

const worker = await Worker.create({
// ...
});

It is completely up to you how to get the clientCert and clientKey pair into your code, whether it is reading from filesystem, secrets manager, or both. Just keep in mind that they are whitespace sensitive and some environment variable systems have been known to cause frustration because they modify whitespace.

Example code that works for local dev and for certs hosted on AWS S3
let serverRootCACertificate: Buffer | undefined;
let clientCertificate: Buffer | undefined;
let clientKey: Buffer | undefined;
if (certificateS3Bucket) {
const s3 = new S3client({ region: certificateS3BucketRegion });
serverRootCACertificate = await s3.getObject({
bucket: certificateS3Bucket,
key: serverRootCACertificatePath,
});
clientCertificate = await s3.getObject({
bucket: certificateS3Bucket,
key: clientCertPath,
});
clientKey = await s3.getObject({
bucket: certificateS3Bucket,
key: clientKeyPath,
});
} else {
serverRootCACertificate = fs.readFileSync(serverRootCACertificatePath);
clientCertificate = fs.readFileSync(clientCertPath);
clientKey = fs.readFileSync(clientKeyPath);
}

Thanks to our Design Partner [Mina Abadir](https://twitter.com/abadir) for sharing this._

Connecting to Temporal Cloud (with mTLS)

The Hello World mTLS sample demonstrates sample code used to connect to a Temporal Cloud account. When signing up to Temporal Cloud you should receive a namespace, a server address and a client certificate and key. Use the following environment variables to set up the sample:

  • TEMPORAL_ADDRESS: looks like foo.bar.tmprl.cloud (NOT web.foo.bar.tmprl.cloud)
  • TEMPORAL_NAMESPACE: looks like foo.bar
  • TEMPORAL_CLIENT_CERT_PATH: e.g. /tls/ca.pem (file contents start with -----BEGIN CERTIFICATE-----)
  • TEMPORAL_CLIENT_KEY_PATH: e.g. /tls/ca.key (file contents start with -----BEGIN PRIVATE KEY-----)

You can leave the remaining vars, like TEMPORAL_SERVER_NAME_OVERRIDE and TEMPORAL_SERVER_ROOT_CA_CERT_PATH blank. There is another var, TEMPORAL_TASK_QUEUE, which the example defaults to 'hello-world-mtls' but you can customize as needed.

Example environment settings
export function getEnv(): Env {
return {
address: 'foo.bar.tmprl.cloud', // NOT web.foo.bar.tmprl.cloud
namespace: 'foo.bar', // as assigned
clientCertPath: 'foobar.pem', // in project root
clientKeyPath: 'foobar.key', // in project root
taskQueue: process.env.TEMPORAL_TASK_QUEUE || 'hello-world-mtls', // just to ensure task queue is same on client and worker, totally optional
// // not usually needed
// serverNameOverride: process.env.TEMPORAL_SERVER_NAME_OVERRIDE,
// serverRootCACertificatePath: process.env.TEMPORAL_SERVER_ROOT_CA_CERT_PATH,
};
}

If you have misconfigured your connection somehow, you will get an opaque [TransportError: transport error] error. Read through your settings carefully and contact us if you are sure you have checked everything.

Note the difference between the gRPC and Temporal Web endpoints:

  • The gRPC endpoint has a DNS address of <Namespace ID>.tmprl.cloud, for example: accounting-production.f45a2.tmprl.cloud.
  • The Temporal Web endpoint is web.<Namespace ID>.tmprl.cloud, for example: https://web.accounting-production.f45a2.tmprl.cloud.

Local mTLS sample tutorial

Follow this tutorial for setting up mTLS (Mutual TLS authentication) with Temporal Server, Client, and Worker locally. For Temporal Cloud customers, there is a separate tutorial above.

  1. Set up Temporal Server with mTLS encryption locally
    • Clone the customization samples repo and change to the tls/tls-simple directory
    • Follow these instructions to set up a local server with mTLS
    • The sample does not register the default namespace on startup, register it with: docker exec -it tls-simple_temporal-admin-tools_1 tctl n re --retention 1 default
  2. Configure your Temporal Client and Worker to connect with mTLS
    • Scaffold a new Temporal project with npx @temporalio/create@latest using the hello-world-mtls template, or copy the relevant configuration from the snippets below into an existing project.
    • Export the required environment variables:
      export TEMPORAL_ADDRESS=localhost
      export TEMPORAL_NAMESPACE=default
      export TEMPORAL_CLIENT_CERT_PATH=/path/to/customization-samples/tls/tls-simple/certs/client.pem
      export TEMPORAL_CLIENT_KEY_PATH=/path/to/customization-samples/tls/tls-simple/certs/client.key
      # just for the local mTLS sample
      export TEMPORAL_SERVER_ROOT_CA_CERT_PATH=/path/to/customization-samples/tls/tls-simple/certs/ca.cert
      export TEMPORAL_SERVER_NAME_OVERRIDE=tls-sample
  3. Test the connection with npm run start.watch and npm run workflow. You should see everything working as per the regular Hello World tutorial.

Temporal has no opinions on production deployment strategy other than the connections and architecture displayed here.

Encryption at rest with DataConverter

Not yet implemented

Temporal has a custom Data Converter feature that lets you implement customized serialization formats and encrypt and decrypt your data. However it is not yet supported in this SDK.

Workflow method arguments and return values are serializable to a Payload protobuf that contains a bytearray as well as metadata map.

You can customize how this is serialized with the SDK's DataConverter interface to do this, including using custom encryption at rest. The default implementation uses JSON serializer, but you can use any alternative serialization mechanism.

If your arguments and return values are encrypted, you will have to run your custom Data Converter again when viewing it in the WebUI as well:

export TEMPORAL_CLI_PLUGIN_DATA_CONVERTER=<path to dataconverter plugin>

./tctl dataconverter web --web_ui_url http://localhost

To configure your Web UI session to use the local data converter use this URL: http://localhost/data-converter/33977

Following this link from the output will enable dataconverter for Temporal Web. Temporal Web will communicate to tctl through websocket to decrypt the data and show it on the UI.

Get notified of updates