5.3 KiB
Temporal OAuth Configuration
This document explains the Temporal OAuth configuration keys, how they are used, and why they are separate from the application's existing OIDC settings.
Why Temporal OAuth is separate from OIDC login
The existing OIDC configuration in this repo powers interactive, user-facing login using the Authorization Code + PKCE flow. Temporal authentication is a different problem:
- OIDC config is tied to HTTP browser redirects, sessions, and user identity. Temporal is a long-running backend client/worker that has no browser context.
- OIDC config uses an IdP issuer URL and relies on OIDC discovery; Temporal needs a direct token endpoint because it must get tokens on its own.
- User tokens represent a specific person and are scoped for web UI access. Temporal needs a service-to-service token with
default:worker,default:read, anddefault:writeclaims. - Reusing the same OAuth2 client for both the web login and the Temporal worker is possible, but it increases blast radius if you accidentally mint worker permissions into user tokens.
- Temporal requires an
Authorization: Bearer <token>header on every gRPC call; the web login flow does not produce a reusable token for the worker process.
Because of those differences, Temporal auth uses the OAuth2 Client Credentials flow and is configured separately from interactive OIDC login.
Configuration keys
These settings are read by cmd/start.go and used when the Temporal client is created in internal/workflows/client.go.
Required when Temporal auth is enabled
-
temporal-oauth-token-url- Full OAuth2 token endpoint URL (no discovery).
- Example:
https://idp.example.com/oauth2/token - Used as the POST target for
grant_type=client_credentials.
-
temporal-oauth-client-id- OAuth2 client ID for the Temporal service client.
- Recommended: a dedicated client with Temporal-specific permissions.
- Supported: reuse the same OAuth2 client as the web application, as long as roles/mappers ensure only the service account token gets Temporal worker permissions (see
Keycloak-setup.md).
-
temporal-oauth-client-secret(ortemporal-oauth-client-secret-file)- Client secret for the Temporal OAuth client.
temporal-oauth-client-secret-filelets you load the secret from a file (it is read and trimmed at startup).
Optional
temporal-oauth-scopes- List of OAuth2 scopes to request.
- These are joined with spaces into a single
scopeform field. - Some providers use scopes to mint claims; others rely on server-side mapping. If your IdP does not use scopes, leave this empty.
Related (non-OAuth) Temporal settings
-
temporal-host- Host:port for the Temporal server (e.g.,
localhost:7233). - If empty, Temporal is disabled entirely.
- Host:port for the Temporal server (e.g.,
-
temporal-namespace- Temporal namespace to target (default:
default).
- Temporal namespace to target (default:
Runtime behavior
The Temporal client uses an OAuthTokenProvider that implements client.HeadersProvider:
- Tokens are fetched with
grant_type=client_credentialsand cached in memory. - The provider respects the
expires_infield from the IdP response. - Tokens are refreshed early if they are within 30 seconds of expiry.
GetHeadersis guarded by a mutex so concurrent worker calls share the same cached token.- Each Temporal gRPC request gets an
authorizationheader:Authorization: Bearer <access_token>
If expires_in is missing or zero, the token is treated as immediately expired and will be re-fetched on the next call.
Reusing the same OAuth2 client (optional)
You can reuse a single OAuth2 client for:
- web login (Authorization Code + PKCE) and
- the Temporal worker/service account (Client Credentials)
as long as the IdP is configured so that:
- the service account token includes the
permissionsclaim required by Temporal (e.g.,default:worker,default:read,default:write), and - human user tokens do not accidentally include
default:worker(or other elevated Temporal permissions).
In the current implementation, the Temporal SDK always uses the client-credentials token for gRPC calls (Temporal will see the service account principal, not the end-user principal).
For a Keycloak setup that supports both approaches (shared client vs separate clients) and emits the required permissions[] claim using a client scope + mapper, see Keycloak-setup.md.
Examples
YAML config (mc-config.yaml)
temporal-host: "temporal.example.com:7233"
temporal-namespace: "default"
temporal-oauth-token-url: "https://idp.example.com/oauth2/token"
temporal-oauth-client-id: "member-console-temporal"
temporal-oauth-client-secret: "replace-me"
temporal-oauth-scopes:
- "temporal.read"
- "temporal.write"
CLI flags
member-console start \
--temporal-host temporal.example.com:7233 \
--temporal-namespace default \
--temporal-oauth-token-url https://idp.example.com/oauth2/token \
--temporal-oauth-client-id member-console-temporal \
--temporal-oauth-client-secret-file /run/secrets/temporal_client_secret \
--temporal-oauth-scopes temporal.read,temporal.write
When to enable
Enable Temporal OAuth when your Temporal server uses the default authorizer + claims mapper (or any authorizer that requires an Authorization header). If you set temporal-host without the OAuth keys, the Temporal client will connect without auth headers and requests will be rejected by the server.