Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions docs/production-deployment/self-hosted-guide/security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,105 @@ Related read:

- [How to secure a Temporal Service](/security)

### What is a TokenProvider Plugin? {#token-provider}

The Token Provider component is a pluggable component that attaches an authentication token to outbound cross-cluster RPCs.
Where the `ClaimMapper` and `Authorizer` plugins protect the receiving end of a request, `TokenProvider` protects the sending end.
When a Temporal Service replicates Workflows, Schedules, or Namespaces to a peer cluster, `TokenProvider` supplies the bearer token that the peer's `ClaimMapper` validates.

A typical approach pairs a JWT-emitting `TokenProvider` on the sender with the [default JWT `ClaimMapper`](#default-jwt-claimmapper) on the receiver.

`TokenProvider` is a single-method interface:

```go
type TokenProvider interface {
GetToken(ctx context.Context, rpcAddress string) (token string, expiresAt time.Time, err error)
}
```

`GetToken` is called for each outbound cross-cluster connection.

- `rpcAddress` is the receiver cluster's `host:port`. Providers commonly use this value as the JWT's `aud` claim to scope each token to a specific receiver.
- `expiresAt` lets the credential cache rotate tokens before they expire. Return `time.Time{}` if the token never expires.

#### Required claims on the receiver

Cross-cluster RPCs target the receiver's `AdminService`, which the default `Authorizer` only admits with `Claims{System: RoleAdmin}`.
Tokens carrying lower roles are rejected.

With the default JWT `ClaimMapper`, the token must carry a `permissions` claim with the entry `temporal-system:admin`.

```
{
"permissions":[
"temporal-system:admin"
]
}
```

A custom `ClaimMapper` can recognize any JWT shape, such as an OAuth-style `scp` scope or a custom claim, and translate it into the same `Claims{System: RoleAdmin}` result.

#### Stream lifecycle

Cross-cluster replication runs over long-lived gRPC streams.
The token is attached when a stream is opened, and the receiver's `ClaimMapper` and `Authorizer` evaluate it at that point.
Already-open streams are not re-checked when the token later expires.
Rotation and revocation therefore only take effect when a stream is reestablished, for example after a reconnect or a process restart.

#### TokenCredentials caching

Tokens returned by `GetToken` are cached per outbound connection by `auth.TokenCredentials`.
The credential refreshes proactively within a configurable grace window before expiry.
If a fetch fails before the token is hard-expired, the credential falls back to the last cached token.

Configure the refresh behavior through `global.authorization.remoteClusterAuth.graceWindow`, a Go duration string such as `30s` or `5m`.
The default is `30s`.

#### Transport security

`auth.TokenCredentials` requires transport security, so the gRPC runtime refuses to attach the credential to a plaintext dial.
Configuring `WithTokenProvider` therefore requires also configuring TLS for the destination, either through [`global.tls.remoteClusters`](/references/configuration) in the YAML config or by passing a custom provider through [`temporal.WithTLSConfigFactory`](/references/server-options#withtlsconfigfactory).

:::note

If `WithTokenProvider` is set but no remote-cluster TLS source is configured, the Temporal Service fails to boot with a directed error message.
This catches the misconfiguration at startup rather than on the first cross-cluster RPC.

:::

#### Fail-closed mode

Set `global.authorization.remoteClusterAuth.require: true` to require a non-empty token on every outbound remote-cluster RPC.
When `require` is `true`, the Temporal Service refuses to start if no `TokenProvider` is configured, and the credential returns `Unauthenticated` rather than sending an empty `authorization` header.

#### JWT helpers

For providers that emit JWTs, `auth.ParseJWTExpiry(token string) time.Time` extracts the `exp` claim without verifying the signature.
This lets a `TokenProvider` return the JWT's own expiry as the `expiresAt` so cache rotation tracks the token's real lifetime.

#### Configuration

Configure your `TokenProvider` with the [`temporal.WithTokenProvider`](/references/server-options#withtokenprovider) server option.
Pair it with the receiver-side plugins so peers validate what the sender attaches.

```go
temporalServer, err := temporal.NewServer(
temporal.WithTokenProvider(myTokenProvider),
temporal.WithAuthorizer(authorization.NewDefaultAuthorizer()),
temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper {
logger := getYourLogger()
return authorization.NewDefaultJWTClaimMapper(
authorization.NewDefaultTokenKeyProvider(cfg, logger),
cfg,
logger,
)
}),
)
```

When `TokenProvider` is not configured, outbound cross-cluster RPCs carry no `authorization` header.
This is the default for deployments that don't replicate to peers, or that rely on transport-level (mTLS) authentication alone.

## Data Converter {#data-converter}

Each Temporal SDK provides a [Data Converter](/dataconversion) that can be customized with a custom [Payload Codec](/payload-codec) to encode and secure your data.
Expand Down
75 changes: 75 additions & 0 deletions docs/references/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,81 @@ global:
- `internode.client.rootCaFiles`
- `frontend.server.clientCaFiles`

### authorization

The `authorization` section configures the Temporal Service's authentication.
It selects the pluggable components that validate incoming gRPC tokens, the signing keys to trust, and the credentials this server attaches to outbound cross-cluster replication RPCs.

It contains two structural subsections that mirror the direction of traffic:

- [`jwtKeyProvider`](#jwtkeyprovider): Supplies the signing keys used to verify inbound JWTs.
- [`remoteClusterAuth`](#remoteclusterauth): Controls the bearer tokens attached to outbound cross-cluster RPCs.

The top-level fields select and tune the inbound plugins:

- `authorizer` - _string_ - _Default:_ `""`.
Selects the inbound authorizer. Empty string disables authorization (the no-op authorizer permits every request); `default` enables Temporal's built-in role-based authorizer. The value is case-insensitive.
- `claimMapper` - _string_ - _Default:_ `""`.
Selects the `ClaimMapper` that extracts roles from a verified token. Empty string disables claim mapping; `default` enables the built-in JWT `ClaimMapper`.
- `audience` - _string_ - _Default:_ `""`.
Required `aud` claim value that inbound JWTs must contain. When empty, audience validation is skipped.
- `authHeaderName` - _string_ - _Default:_ `authorization`.
gRPC metadata header from which the `ClaimMapper` reads the bearer token.

See [How to secure a Temporal Service](/self-hosted-guide/security) for the conceptual model behind `ClaimMapper`, `Authorizer`, and `TokenProvider` and how the plugins fit together.

A minimal example that enables JWT-based inbound auth using Temporal's defaults:

```yaml
global:
authorization:
jwtKeyProvider:
keySourceURIs:
- https://idp.example.com/.well-known/jwks.json
refreshInterval: 1m
authorizer: default
claimMapper: default
audience: temporal-frontend
```

#### jwtKeyProvider

Configures the source of signing keys used by the default `ClaimMapper` to verify inbound JWTs.

- `keySourceURIs` - _list of strings_.
URLs to fetch JWKS-formatted public keys from. The default `ClaimMapper` fetches and caches the union of keys returned by each URI.
- `refreshInterval` - _Go duration string_ (for example `1m`, `5m`) - _Default:_ `0`.
How often the key set is refetched. Zero (the default) disables periodic refresh, so keys are loaded once at startup and never rotated.

#### remoteClusterAuth

Controls outbound bearer tokens carried on cross-cluster RPCs by a [`TokenProvider`](/self-hosted-guide/security#token-provider).
This block has no effect unless a `TokenProvider` is also configured via [`temporal.WithTokenProvider`](/references/server-options#withtokenprovider).

- `require` - _boolean_ - _Default:_ `false`.
When `true`, every outbound cross-cluster RPC must carry a non-empty token; the credential returns `Unauthenticated` rather than sending an empty `authorization` header.
The Temporal Service refuses to start when `require` is `true` but no `TokenProvider` is configured.
- `graceWindow` - _Go duration string_ (for example `30s`, `5m`) - _Default:_ `30s`.
How long before a token's `expiresAt` the credential cache refreshes proactively.
If a refresh fails, the credential falls back to the last cached token until its hard expiry.

A combined example pairing inbound JWT validation with outbound replication-stream auth on a sender cluster:

```yaml
global:
authorization:
jwtKeyProvider:
keySourceURIs:
- https://idp.example.com/.well-known/jwks.json
refreshInterval: 1m
authorizer: default
claimMapper: default
audience: temporal-frontend
remoteClusterAuth:
require: true
graceWindow: 30s
```

## persistence

The `persistence` section holds configuration for the data store/persistence layer.
Expand Down
11 changes: 11 additions & 0 deletions docs/references/server-options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ s, err := temporal.NewServer(
)
```

### WithTokenProvider

Configures a [`TokenProvider`](/self-hosted-guide/security#token-provider) that supplies bearer tokens for outbound cross-cluster RPCs.
`TokenProvider` is defined in the `go.temporal.io/server/common/rpc/auth` package.

```go
s, err := temporal.NewServer(
temporal.WithTokenProvider(myTokenProvider),
)
```

### WithCustomMetricsReporter

Sets a custom tally metric reporter.
Expand Down
Loading