Files
member-console/internal/workflows/TEMPORAL_AUTH.md
T

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, and default:write claims.
  • 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 (or temporal-oauth-client-secret-file)

    • Client secret for the Temporal OAuth client.
    • temporal-oauth-client-secret-file lets 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 scope form field.
    • Some providers use scopes to mint claims; others rely on server-side mapping. If your IdP does not use scopes, leave this empty.
  • temporal-host

    • Host:port for the Temporal server (e.g., localhost:7233).
    • If empty, Temporal is disabled entirely.
  • temporal-namespace

    • Temporal namespace to target (default: default).

Runtime behavior

The Temporal client uses an OAuthTokenProvider that implements client.HeadersProvider:

  • Tokens are fetched with grant_type=client_credentials and cached in memory.
  • The provider respects the expires_in field from the IdP response.
  • Tokens are refreshed early if they are within 30 seconds of expiry.
  • GetHeaders is guarded by a mutex so concurrent worker calls share the same cached token.
  • Each Temporal gRPC request gets an authorization header:
    • 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 permissions claim 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.