<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Bitovi Blog - UX and UI design, JavaScript and Frontend development
Loading

Backend |

Implementing Role-Based Authentication for Self-Hosted Temporal

Learn how to implement role-based authentication for self-hosted Temporal. Use custom claim mappers and authorizers to enhance security and access control for your workflows.

Matt Chaffe

Matt Chaffe

Twitter Reddit

Temporal is a powerhouse for orchestrating workflows, offering durability and reliability out of the box. But when you’re running a self-hosted Temporal deployment, security becomes a critical consideration. How do you ensure that only authorized users and services can access specific workflows? The answer lies in implementing robust role-based authentication.

In this post, we’ll break down how to build a custom authentication system for Temporal. You’ll learn how to create a custom authorizer and claim mapper, integrate them into the Temporal server, and deploy everything using Docker and Helm. Let’s dive in.

Why custom authentication for Temporal?

By default, self-hosted Temporal doesn’t enforce authentication, making it flexible but potentially vulnerable in multi-tenant environments. Without authentication, any service or user can interact with workflows, which isn’t ideal in production settings.

A custom authentication layer solves this by:

  • Restricting access based on roles and permissions.
  • Ensuring users and services authenticate via OpenID Connect (OIDC).
  • Keeping security tight without compromising Temporal’s resilience.
Custom authentication is only required for self-hosted Temporal instances. Temporal Cloud handles roles-based auth out of the box. Need help evaluating whether Temporal Cloud is right for you? Schedule a free audit with our Temporal Consultants

How Custom Authentication Works in Temporal

Here’s the plan for adding authentication to your Temporal deployment:

  1. Implement a claim mapper – This component extracts and translates authentication tokens into Temporal’s expected format.
  2. Build an authorizer – The authorizer validates permissions and enforces access controls.
  3. Modify the Temporal server – We’ll hook in our custom authentication components.
  4. Package everything in Docker and deploy with Helm – A clean, scalable way to roll out our solution.

Implementing a Claim Mapper

The claim mapper is responsible for converting OIDC authentication tokens into a format that Temporal understands. This allows the system to recognize users and assign appropriate permissions.

type OIDCClaimMapper struct {
    issuerURL string
    clientID  string
    jwksURL   string
    keySet    jwk.Set
}

func NewOIDCClaimMapper() *OIDCClaimMapper {
    issuerURL := os.Getenv("TEMPORAL_OIDC_ISSUER_URL")
    clientID := os.Getenv("TEMPORAL_OIDC_CLIENT_ID")
    jwksURL := issuerURL + "/.well-known/jwks.json"

    keySet, err := jwk.Fetch(context.Background(), jwksURL)
    if err != nil {
        return nil
    }

    return &OIDCClaimMapper{
        issuerURL: issuerURL,
        clientID:  clientID,
        jwksURL:   jwksURL,
        keySet:    keySet,
    }
}

This mapper ensures that users are correctly identified based on their authentication tokens and assigned roles accordingly.

Implementing an Authorizer

The authorizer is where access control happens. It checks whether a user has permission to perform a requested operation.

type OIDCAuthorizer struct{}

func (a *OIDCAuthorizer) Authorize(ctx context.Context, claims *authorization.Claims, target *authorization.CallTarget) (authorization.Result, error) {
    if authorization.IsHealthCheckAPI(target.APIName) {
        return decisionAllow, nil
    }
    if claims == nil || target.Namespace == "" {
        return decisionAllow, nil
    }

    metadata := api.GetMethodMetadata(target.APIName)     
var userRole authorization.Role switch metadata.Scope { case api.ScopeCluster: userRole = claims.System case api.ScopeNamespace: userRole = claims.System | claims.Namespaces[target.Namespace] default: return decisionDeny, nil } requiredRole := getRequiredRole(metadata.Access) if userRole >= requiredRole { return decisionAllow, nil } return decisionDeny, nil }

With this setup, Temporal ensures that only authorized users can access specific workflows and namespaces.

Integrate Claim Mapper and Authorizer with Temporal Server

Next, we will create a custom Temporal Server that implements the Claim Mapper & Authorizer:

func main() {
	log.Println("🚀 Starting Temporal Server with OIDC Authentication...")

	cfg, err := config.LoadConfig("development", "./config", "")
	if err != nil {
		log.Fatal(err)
	}

	s, err := temporal.NewServer(
		temporal.ForServices(temporal.DefaultServices),
		temporal.WithConfig(cfg),
		temporal.InterruptOn(temporal.InterruptCh()),

		// Inject Custom ClaimMapper
		temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper {
			return NewOIDCClaimMapper()
		}),

		// Inject Custom Authorizer
		temporal.WithAuthorizer(&OIDCAuthorizer{}),
	)
	if err != nil {
		log.Fatal(err)
	}

	err = s.Start()
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Temporal Server Stopped.")
}

Deploying a Custom Authentication Solution in Temporal

Building and Deploying the Docker Image

Now that we’ve built our customer server with the authentication components integrated, let’s package them into a Docker image:

docker build -t temporal-auth-server .
docker push temporal-auth-server:latest

Configuring Helm for Deployment

Next, modify the Helm values file to deploy our custom server and hook up our OIDC Provider:

server:
  image:
    repository: temporal-auth-server
    tag: latest
    pullPolicy: Never
  command: ["/app/temporal-auth-server"]
  additionalEnv:
    - name: TEMPORAL_OIDC_ISSUER_URL
      valueFrom:
        secretKeyRef:
          name: temporal-auth-secrets
          key: issuer_url
    - name: TEMPORAL_OIDC_CLIENT_ID
      valueFrom:
        secretKeyRef:
          name: temporal-auth-secrets
          key: client_id

Finally, apply the Helm chart:

helm upgrade install temporal temporal/temporal -f ./k8s/dev/values-dev.yaml -n temporal

Is self-hosted Temporal the right approach for your project?

If you are deploying self-hosted Temporal in production, you should always use OIDC to restrict user access.

Consider using this claim mapper and authorizer approach if:

  • You need fine-grained access control for different teams and workflows.
  • Your system relies on OIDC authentication to integrate with identity providers.
  • You want to enforce security boundaries across multiple namespaces.

Need help implementing authentication for Temporal?

Security is critical when deploying Temporal at scale, and getting authentication right can be complex. If you’re looking for expert guidance, Bitovi’s Temporal consultants can help.

Tell us about your project, and we’ll set up a free consultation to help you design a secure, scalable Temporal deployment tailored to your needs.