---
component: ROOT
version: "2026.3"
slug: ROOT/administration/workload-identity
canonical_url: "https://docs.develocity.ai/2026.3/administration/access-control/workload-identity/"
title: "Workload Identity"
description: "Replace CI access keys with short-lived OIDC tokens. Configure Develocity to validate tokens from GitHub Actions and other OIDC providers."
keywords:
  - "OIDC"
  - "access control"
  - "continuous integration"
  - "Universal Cache"
status: current
---

<!-- llms-index: https://docs.develocity.ai/llms.txt -->

# Workload Identity

<a id="preamble"></a>

Configure workload identity rules to let CI workloads authenticate with Develocity using short-lived OIDC tokens instead of long-lived access keys.

<a id="overview"></a>

## Overview

Workload identity federation is based on the [OpenID Connect (OIDC) standard](https://openid.net/connect/). Instead of storing a long-lived Develocity access key, you exchange a short-lived OIDC token from your CI provider for a Develocity access token.

The exchange works in these steps:

1.  The CI provider issues a short-lived OIDC token at the start of the job. A GitHub Actions token lasts about five minutes.
    
2.  The CI integration exchanges the token for a Develocity access token via a `POST /api/auth/token` request.
    
3.  The build authenticates with the returned Develocity access token for the rest of the run.
    

Your CI job mints the OIDC token from your CI provider; no Develocity plugin mints it for you.

You supply that token in place of your access key, in host-qualified form, and the CI integration exchanges it for a Develocity access token the same way it exchanges an access key.

For how each provider mints and supplies the token, see the provider examples below and the [Continuous Integration](https://docs.develocity.ai/2026.3/integrations/ci/) section.

Because the OIDC token is issued fresh for each job and is short-lived, there is no shared secret to store, rotate, or leak. Claim requirements let you scope a rule to a specific organization, repository, branch, or workflow, so each workload receives only the access it needs.

Use workload identity when your CI provider issues OIDC tokens and you want to remove long-lived Develocity access keys from CI configuration, or grant roles based on which repository, branch, or workflow is authenticating.

> [!NOTE]
> You can present the OIDC token directly in place of an access key, but it usually expires partway through a build, which is why the CI integration exchanges it for a longer-lived Develocity access token. On a CI system with no integration that performs the exchange, see Other CI Systems for the direct path.

<a id="prerequisites"></a>

## Prerequisites

*   An account with permission to configure access control in Develocity.
    
*   A CI provider that issues OIDC tokens (GitHub Actions, Google Cloud Workload Identity, HashiCorp Vault, and similar).
    
*   Network connectivity from Develocity to the issuer’s JWKS endpoint (see [Issuer Key-Refresh Failures](#issuer-key-refresh-failures)).
    

<a id="configure-via-the-user-interface"></a>

## Configure via the User Interface

Navigate to **Administration**  **Access control**  **Workload identity** and select **Add**.

> [!NOTE]
> Develocity evaluates each incoming token against every configured rule for its issuer. A rule matches when the issuer, audience, and all claim requirements match. If multiple rules match, Develocity applies all of them and grants the union of their roles and project groups.

1.  **ID:** Enter a unique identifier for the rule.
    
2.  **Issuer:** Enter the `iss` claim value from the provider’s tokens (for example, `https://token.actions.githubusercontent.com` for GitHub Actions). Select **Test** to verify Develocity can reach the issuer’s JWKS endpoint before saving. If Develocity cannot reach the JWKS endpoint, see [Issuer Key-Refresh Failures](#issuer-key-refresh-failures).
    
3.  **Audience:** Enter the `aud` claim the JWT must carry. The workload requests this value from its provider at token issuance time.
    
4.  **Assigned roles** and **Assigned project groups:** Select the roles and project groups to grant to every token that matches this rule.
    
5.  **Roles claim** and **Project groups claim:** Optionally enter a claim name for dynamic grants. At authentication time, Develocity reads that claim from the token and matches its values against the [identity provider (IdP)](https://docs.develocity.ai/2026.3/administration/access-control/identity-provider/) attribute values configured on each role or project group. Both string and list claims are supported: a list-valued claim grants all roles or project groups whose IdP attribute value matches any element.
    

After you save the rule, Develocity opens the claim requirements screen. Add one or more claim requirements that must all match. Available match types are Equals, Starts With, Ends With, Contains, and Regular Expression.

> [!IMPORTANT]
> A rule with no claim requirements never matches any token. At least one claim requirement must be added before the rule can authenticate a workload. If you truly need to accept tokens with no other claim criteria, you can add a requirement for the issuer or audience claim.

Each rule also carries **Read isolation level** and **Write isolation level** under **Universal Cache isolation**, which appear only while trusted artifact isolation is on. See [Universal Cache Isolation](#universal-cache-isolation).

> [!IMPORTANT]
> Save stores the rule as a draft without applying it. A banner then appears at the top of the page: There are saved configuration changes that have not been applied. Select Apply in that banner to put the rule into effect. If you leave the page or select Discard first, you lose the change. Develocity does not restart when you apply a workload identity change.

<a id="claim-requirement-match-types"></a>

### Claim Requirement Match Types

 
| Match Type | Behavior |
| --- | --- |
| Equals | Claim value must match exactly. |
| Starts With | Claim value must begin with the specified string. |
| Ends With | Claim value must end with the specified string. |
| Contains | Claim value must include the specified string. |
| Regular Expression | Claim value must match the regular expression. |

Starts With, Ends With, and Contains compare the requirement string literally against the claim value. The boundary character is not a special or stripped token; it is simply the character that bounds a segment in the claim’s own format, matched as part of the required string. For example, the `:` in `repo:acme/acme-app:`, a `/` in a path, or an `@` in a workflow ref each follow from the claim’s own format. Any character can serve as the boundary as long as it naturally ends a segment in the real claim value.

> [!IMPORTANT]
> Include the boundary character at the end of a Starts With string (or the start of an Ends With string), or the requirement over-matches. A Starts With requirement of repo:acme/acme-app also matches repo:acme/acme-app-fork because the match is a literal prefix check. Adding the trailing : (repo:acme/acme-app:) stops the match at the segment boundary. Develocity does not enforce this. It accepts a requirement with no boundary character and silently matches every value that shares the prefix, suffix, or substring. It does not reject the configuration.

<a id="provider-examples"></a>

## Provider Examples

Each provider emits its own set of claims. Configure the issuer and audience, then add claim requirements that scope the rule to an organization, a repository, or a branch. Develocity reads each required claim as a string, so scope only on string-valued claims: numeric, boolean, and array claims are treated as absent and the requirement fails.

GitHub Actions

GitHub Actions tokens use the issuer `https://token.actions.githubusercontent.com`. Set the Develocity audience to the value your workflow passes to `getIDToken()`; it defaults to the repository owner URL, such as `https://github.com/acme`. Scope on the dedicated string claims `repository_owner`, `repository`, `ref`, `workflow_ref`, and `job_workflow_ref`.

 
| Goal | Claim requirement(s) |
| --- | --- |
| Limit to an organization | `repository_owner` Equals `acme` |
| Limit to a single repository | `repository` Equals `acme/acme-app` |
| Limit to a single branch | `repository` Equals `acme/acme-app` and `ref` Equals `refs/heads/main` |
| Limit to a single workflow file | `workflow_ref` Starts With `acme/acme-app/.github/workflows/release.yml@` |
| Limit to a shared reusable workflow | `job_workflow_ref` Starts With `acme/ci-workflows/.github/workflows/build.yml@` |

The `workflow_ref` and `job_workflow_ref` claims both use the format `{owner}/{repo}/.github/workflows/{file}@{ref}`. `workflow_ref` identifies the top-level workflow that started the run, so scoping on it pins authentication to one workflow file and rejects every other workflow in the repository. `job_workflow_ref` identifies the reusable workflow the job actually calls, which lets a central repository own the build logic and authorize only jobs that run it. Include the trailing `@` when scoping to a file regardless of ref, or use Equals with a full `@{ref}` suffix to pin a specific ref.

Do not scope on `sub`. GitHub gives repositories created after 2026-07-15 an immutable `sub` that embeds numeric owner and repository IDs, so a `sub` Starts With rule written for the older format stops matching with no warning. The claims `ref_type` (`branch` or `tag`) and `environment` are also string-valued if you need to narrow further.

For the workflow setup that mints and supplies the token, see [Authenticate With Workload Identity](https://docs.develocity.ai/2026.3/integrations/ci/github-actions/#authenticate-with-workload-identity), or the [GitHub Actions OpenID Connect documentation](https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect) for the underlying GitHub mechanism.

GitLab CI

GitLab CI tokens use the GitLab instance URL as the issuer, such as `https://gitlab.com` or `https://gitlab.acme.org`. Set the Develocity audience to the value you configure under `aud:` in the job’s `id_tokens` block. Scope on the string claims `namespace_path`, `project_path`, and `ref`.

 
| Goal | Claim requirement(s) |
| --- | --- |
| Limit to a group or namespace | `namespace_path` Equals `acme` |
| Limit to a single project | `project_path` Equals `acme/acme-app` |
| Limit to a single branch | `project_path` Equals `acme/acme-app` and `ref` Equals `main` |

To also exclude tag pipelines, add `ref_type` Equals `branch`. GitLab emits `project_id` and `namespace_id` as string values that survive renames, so use them with Equals when rename safety matters more than readable path-based rules.

For the job setup that mints and supplies the token, see [Authenticate With Workload Identity](https://docs.develocity.ai/2026.3/integrations/ci/gitlab-ci/#authenticate-with-workload-identity), or the [GitLab ID token authentication documentation](https://docs.gitlab.com/ci/secrets/id_token_authentication/) for the underlying GitLab mechanism.

Jenkins

The [OpenID Connect Provider plugin](https://plugins.jenkins.io/oidc-provider/) makes Jenkins an OIDC issuer. Its issuer is your Jenkins URL followed by `/oidc`, such as `https://jenkins.example.com/oidc`, and you set the audience per credential on the OpenID Connect ID Token credential in Jenkins.

By default the plugin sets `sub` to the job URL (`JOB_URL`) and a numeric `build_number` that Develocity cannot match, and it exposes no repository or branch claims, so scope on `sub`. `JOB_URL` always ends in a trailing slash, and for a multibranch pipeline the branch is a child job whose name is percent-encoded, such as `https://jenkins.example.com/job/acme-app/job/main/`.

 
| Goal | Claim requirement(s) |
| --- | --- |
| Limit to all jobs in a folder | `sub` Starts With `https://jenkins.example.com/job/acme-team/` |
| Limit to a single job | `sub` Equals `https://jenkins.example.com/job/acme-app/` |
| Limit to all branches of a multibranch pipeline | `sub` Starts With `https://jenkins.example.com/job/acme-app/` |
| Limit to a single branch | `sub` Equals `https://jenkins.example.com/job/acme-app/job/main/` |

Always include the trailing slash on an Equals value. To scope on anything other than the job URL, add a custom claim template in Jenkins under **Manage Jenkins**  **Security**  **ID Token** that exposes a claim such as `branch`. Admins who store the credential on a folder can use the folder-scoped issuer, which appends the folder path to the issuer, such as `https://jenkins.example.com/oidc/job/acme-team`.

For the pipeline setup that binds and supplies the token, see [Authenticate With Workload Identity](https://docs.develocity.ai/2026.3/integrations/ci/jenkins/#authenticate-with-workload-identity).

<a id="universal-cache-isolation"></a>

## Universal Cache Isolation

The Workload identity page also carries the controls for [trusted artifact isolation](https://docs.develocity.ai/2026.3/administration/build-acceleration/trusted-artifact-isolation/), which splits every Universal Cache key into a trusted entry and an untrusted one. They live here because isolation levels come from claim matching, so they can only be assigned to a workload identity rule, never to an access key.

Under **Universal Cache isolation**:

*   **Enable Universal Cache isolation** turns isolation on for the installation. It is off by default, and upgrading does not turn it on.
    
*   **Read isolation level** and **Write isolation level** on each rule decide which of the two entries a matching workload writes, and which it may read. Develocity hides these per-rule controls while isolation is off.
    

Enabling isolation changes cache behavior for every workload, and the order in which you enable it and set the levels matters. For what each level does, the one combination Develocity refuses, which levels suit each kind of workload, and the procedure for turning isolation on, see [Trusted Artifact Isolation](https://docs.develocity.ai/2026.3/administration/build-acceleration/trusted-artifact-isolation/).

<a id="troubleshooting"></a>

## Troubleshooting

A problem reported against a workload identity rule is usually one of two things: Develocity cannot reach the rule’s issuer, or the rule’s isolation levels are producing cache behavior you did not expect.

<a id="issuer-key-refresh-failures"></a>

### Issuer Key-Refresh Failures

Develocity displays an admin notice when it cannot refresh signing keys from a configured issuer. Develocity fetches signing keys (JWKS) from each configured issuer and retries every minute if the fetch fails. The admin notice appears once an issuer has been unreachable for 15 minutes. After 24 hours without a successful refresh, Develocity drops the cached keys and token validation for that issuer fails, breaking any builds that use it.

To diagnose:

1.  Navigate to **Administration**  **Access control**  **Workload identity**. The rule list shows the JWKS status per issuer.
    
2.  Select **Test** on the affected rule to attempt a live fetch and see the error.
    
3.  Check Develocity server logs for WARN-level messages from `WorkloadIdentityRegistry`.
    

Common causes:

*   The OIDC configuration endpoint at `{issuer}/.well-known/openid-configuration` is unreachable from the Develocity host (firewall, egress proxy, or DNS).
    
*   The JWKS endpoint (from the `jwks_uri` field in the discovery document) is unreachable.
    
*   The issuer URL is wrong: typo, trailing-slash mismatch, or HTTP instead of HTTPS.
    
*   A TLS trust failure: self-signed or expired certificate, or missing intermediate CA.
    

> [!NOTE]
> Per the OpenID Connect Discovery 1.0 spec, the issuer must be an HTTPS URL with no query string or fragment, and must match the iss claim in tokens exactly, including any trailing slash. Develocity fetches {issuer}/.well-known/openid-configuration and reads the jwks\_uri field from the JSON response. It then fetches the JWK Set at that URI, which must be a JSON document with a keys array containing RSA or EC public keys. Individual keys that cannot be parsed are logged and skipped; if all keys in the set fail to parse, the refresh fails.

Fix the issuer URL or network path, or add the CA certificate to Develocity’s trusted store if TLS is the cause. Develocity retries every minute and clears the notice once a fetch succeeds.

<a id="unexpected-cache-behavior"></a>

### Unexpected Cache Behavior

Trusted artifact isolation decides what a build may read and write from the levels on the rule its credentials matched, so a mistake in those levels surfaces as a cache problem rather than an authentication one. A build that starts missing every cache key, a cache response of `409 Conflict` carrying an `X-Cache-Isolation-Conflict` header, and a job whose Artifact Cache summary reports zero trusted entries all share that shape: the token validated and the rule matched, and only the isolation levels are wrong.

Diagnose these from [Trusted Artifact Isolation](https://docs.develocity.ai/2026.3/administration/build-acceleration/trusted-artifact-isolation/), which gives the cause and the remedy for each.

<a id="related-documentation"></a>

## Related Documentation

*   [Identity Provider](https://docs.develocity.ai/2026.3/administration/access-control/identity-provider/)
    
*   [System User](https://docs.develocity.ai/2026.3/administration/access-control/system-user/)
    
*   [Permissions and Roles](https://docs.develocity.ai/2026.3/administration/access-control/permissions-and-roles/)
    
*   [Project-Level Access Control](https://docs.develocity.ai/2026.3/administration/access-control/project-level-access-control/)
    
*   [Trusted Artifact Isolation](https://docs.develocity.ai/2026.3/administration/build-acceleration/trusted-artifact-isolation/)