Try it free

Workload identity federation

  • Latest Dynatrace
  • Reference
  • 1-min read
  • Published Aug 06, 2026
  • Preview

Workload identity federation (WIF) lets an external workload call the Dynatrace API with a token issued by its own identity provider. Dynatrace resolves that token to a service user, so you never store a Dynatrace client secret or platform token alongside your workload.

This suits any workload that already has a machine identity—an Azure virtual machine with a managed identity, a GitHub Actions job, a Google Cloud service, or a Kubernetes pod with a projected service account token.

You configure workload identity federation through the Account Management API.

How it works

  1. The workload obtains a short-lived OpenID Connect token from its own identity provider.
  2. The workload calls the Dynatrace API, passing that token in the Authorization header as a bearer token.
  3. Dynatrace looks for a trust policy matching the token's iss and aud claims, then verifies the token signature against the issuer's JSON Web Key Set.
  4. Dynatrace compares the token's claims against the service user mappings on that trust policy.
  5. On a match, Dynatrace runs the request as the mapped service user, limited to the scopes allowed by the mapping.

Steps 3 to 5 happen inside the platform on every request. Your workload only ever handles its own identity provider token.

Because that token is short-lived and issued on demand, no Dynatrace credential is ever written to disk, baked into an image, or stored in a CI secret.

Concepts

ObjectDescription

Trust policy

Declares an external identity provider that Dynatrace trusts, identified by an issuer URL and an audience. A trust policy is unique per issuer URL, and audience combination. One Dynatrace account can have many such trust policies.

Service user mapping

Binds tokens that satisfy a trust policy to one service user, one environment, and a set of scopes. A trust policy can hold many mappings. Same service user can be assigned to mappings from different trust policies, as long as they do not overlap with each other.

Claim mapping

A condition on a single claim in the external token. A mapping needs at least one claim mapping, and all of them must match for the mapping to apply.

Each claim mapping uses a matchType:

  • EXACT—the claim value must match the configured value exactly.
  • GLOB—the claim value is matched against a wildcard pattern, which is useful for matching a group of repositories, branches, or namespaces.

Prerequisites

  • An OAuth client with the account-idm-read and account-idm-write scopes. See OAuth clients.
  • An active service user to receive the federated identity.
  • An identity provider that publishes an OpenID Connect discovery document over HTTPS, and that issues tokens signed with ES256 or RS256.

External tokens must carry the iss, aud, exp, iat, and sub claims.

Manage trust policies

All requests below authenticate with an OAuth bearer token, as described in OAuth clients.

OperationEndpointRequired scope

Create

POST /iam/v1/accounts/{accountUuid}/wif/trust-policies

account-idm-write

List

GET /iam/v1/accounts/{accountUuid}/wif/trust-policies

account-idm-read

Retrieve

GET /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}

account-idm-read

Update

PUT /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}

account-idm-write

Change status

PATCH /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}

account-idm-write

Delete

DELETE /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}

account-idm-write

Create a trust policy

OperationEndpoint

POST

https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies

Content type

application/json

Provide the following parameters in the request body.

ParameterValue

name

Required A name for the trust policy, unique within the account.

issuerUrl

Required The issuer URL of the external identity provider, matched against the iss claim of the external token.

Must use HTTPS and must not contain a query string or a fragment. Dynatrace rejects URLs that resolve to private or loopback addresses. Dynatrace discovers the issuer's JSON Web Key Set automatically from {issuerUrl}/.well-known/openid-configuration.

audience

Required The expected value of the aud claim of the external token. Matched exactly, with no wildcards and no normalization.

description

Optional A free-text description of the trust policy.

curl --request POST \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies' \
--header 'Authorization: Bearer {your-access-token}' \
--header 'Content-Type: application/json' \
--data '{
"name": "Azure production tenant",
"issuerUrl": "https://sts.windows.net/{tenantId}/",
"audience": "api://dynatrace-wif",
"description": "Workloads running in the production Azure subscription"
}'

A successful request returns 201 and the created trust policy.

{
"uuid": "3f1c9a2e-5d47-4b8a-9c31-7e0b2d6f8a15",
"name": "Azure production tenant",
"issuerUrl": "https://sts.windows.net/{tenantId}/",
"issuerUrlHash": "9b74c9897bac770ffc029102a200c5de",
"jwksUri": "https://sts.windows.net/{tenantId}/.well-known/openid-configuration",
"description": "Workloads running in the production Azure subscription",
"audience": "api://dynatrace-wif",
"status": "ACTIVE",
"createdAt": "2026-08-06T09:14:22Z",
"updatedAt": "2026-08-06T09:14:22Z",
"serviceUserMappings": []
}

Dynatrace creates new trust policies with status ACTIVE. The jwksUri field is read-only — Dynatrace derives it automatically from the issuer's OpenID Connect discovery document at {issuerUrl}/.well-known/openid-configuration and you cannot set or override it.

List trust policies

curl --request GET \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies?pageSize=50&pageNumber=1' \
--header 'Authorization: Bearer {your-access-token}'

The response is paginated. Use the pageNumber and pageSize query parameters to page through results.

{
"pageSize": 50,
"pageNumber": 1,
"total": 1,
"results": [
{
"uuid": "3f1c9a2e-5d47-4b8a-9c31-7e0b2d6f8a15",
"name": "Azure production tenant",
"issuerUrl": "https://sts.windows.net/{tenantId}/",
"audience": "api://dynatrace-wif",
"status": "ACTIVE",
"createdAt": "2026-08-06T09:14:22Z",
"updatedAt": "2026-08-06T09:14:22Z"
}
]
}

Retrieve a trust policy

Retrieving a single trust policy also returns its service user mappings.

curl --request GET \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}' \
--header 'Authorization: Bearer {your-access-token}'

Update a trust policy

Send the complete object. Fields you omit are cleared.

curl --request PUT \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}' \
--header 'Authorization: Bearer {your-access-token}' \
--header 'Content-Type: application/json' \
--data '{
"name": "Azure production tenant",
"issuerUrl": "https://sts.windows.net/{tenantId}/",
"audience": "api://dynatrace-wif",
"description": "Updated description"
}'

Enable or disable a trust policy

Disabling a trust policy immediately stops all workloads that rely on it from authenticating, without deleting its mappings. Disabling is the quickest way to cut off access from a compromised identity provider. Note that there still might be a short delay due to caching until this takes effect.

curl --request PATCH \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}' \
--header 'Authorization: Bearer {your-access-token}' \
--header 'Content-Type: application/json' \
--data '{ "status": "INACTIVE" }'

Set status to ACTIVE to re-enable the trust policy.

Delete a trust policy

Deleting a trust policy also removes its service user mappings.

curl --request DELETE \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}' \
--header 'Authorization: Bearer {your-access-token}'

Manage service user mappings

A trust policy establishes who Dynatrace trusts. A service user mapping decides what a trusted workload can do.

OperationEndpointRequired scope

Create

POST /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings

account-idm-write

List

GET /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings

account-idm-read

Retrieve

GET /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings/{mappingUuid}

account-idm-read

Update

PUT /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings/{mappingUuid}

account-idm-write

Change status

PATCH /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings/{mappingUuid}

account-idm-write

Delete

DELETE /iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings/{mappingUuid}

account-idm-write

Create a service user mapping

OperationEndpoint

POST

https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings

Content type

application/json

ParameterValue

serviceUserUuid

Required The UUID of an active service user. Requests that match this mapping run as this user.

environmentId

Required The environment this mapping grants access to.

scopes

Required The service user scope subset that will be assigned to the access token issued for the matching workload.

claimMappings

Required One or more conditions on the claims of the external token. Each entry takes a claimName, a claimValue, and a matchType of either EXACT or GLOB.

All claim mappings must match. Each claimName can appear only once per mapping.

curl --request POST \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings' \
--header 'Authorization: Bearer {your-access-token}' \
--header 'Content-Type: application/json' \
--data '{
"serviceUserUuid": "be820735-3114-4d40-9c44-dfa18fa62be9",
"environmentId": "{environmentId}",
"scopes": ["storage:logs:read", "storage:events:read"],
"claimMappings": [
{
"claimName": "sub",
"claimValue": "11111111-2222-3333-4444-555555555555",
"matchType": "EXACT"
}
]
}'

A successful request returns 201 and the created mapping.

{
"uuid": "7a2b4c6d-8e10-4f32-b5a7-9c1d3e5f7089",
"serviceUserUuid": "be820735-3114-4d40-9c44-dfa18fa62be9",
"environmentId": "{environmentId}",
"scopes": ["storage:logs:read", "storage:events:read"],
"status": "ACTIVE",
"claimMappings": [
{
"claimName": "sub",
"claimValue": "11111111-2222-3333-4444-555555555555",
"matchType": "EXACT"
}
],
"createdAt": "2026-08-06T09:22:41Z",
"updatedAt": "2026-08-06T09:22:41Z"
}

An external token must match exactly one mapping. If it matches none, or more than one, the request fails with an HTTP 401. Keep claim mappings specific enough to stay unambiguous.

To grant a group of workloads the same access, use a GLOB match instead of listing each identity.

{
"claimName": "sub",
"claimValue": "repo:my-org/my-service:ref:refs/heads/*",
"matchType": "GLOB"
}

List service user mappings

curl --request GET \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings?pageSize=50&pageNumber=1' \
--header 'Authorization: Bearer {your-access-token}'

Retrieve a service user mapping

curl --request GET \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings/{mappingUuid}' \
--header 'Authorization: Bearer {your-access-token}'

Update a service user mapping

Send the complete object, including all claim mappings you want to keep.

curl --request PUT \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings/{mappingUuid}' \
--header 'Authorization: Bearer {your-access-token}' \
--header 'Content-Type: application/json' \
--data '{
"serviceUserUuid": "be820735-3114-4d40-9c44-dfa18fa62be9",
"environmentId": "{environmentId}",
"scopes": ["storage:logs:read"],
"claimMappings": [
{
"claimName": "sub",
"claimValue": "11111111-2222-3333-4444-555555555555",
"matchType": "EXACT"
}
]
}'

Enable or disable a service user mapping

curl --request PATCH \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings/{mappingUuid}' \
--header 'Authorization: Bearer {your-access-token}' \
--header 'Content-Type: application/json' \
--data '{ "status": "INACTIVE" }'

Delete a service user mapping

curl --request DELETE \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings/{mappingUuid}' \
--header 'Authorization: Bearer {your-access-token}'

Authenticate a workload

Once a trust policy and a matching service user mapping exist, the workload authenticates by attaching its own identity provider token to the Authorization header, preceded by the Bearer realm. Note that some workloads, like the OTel Collector, already implement libraries that handle the authorization header with every http request, as well as refreshing the OIDC token.

--header 'Authorization: Bearer {your-oidc-token}'

There is nothing to exchange and no Dynatrace token to manage. Dynatrace resolves the token to the mapped service user on each request.

curl --request POST \
--url 'https://{environmentId}.apps.dynatrace.com/platform/storage/query/v1/query:execute' \
--header 'Authorization: Bearer {your-oidc-token}' \
--header 'Content-Type: application/json' \
--data '{ "query": "fetch logs | limit 10" }'

Request a fresh token from your identity provider whenever the current one expires. These tokens are short-lived by design.

Any problem with the token results in an HTTP 401 response. Dynatrace doesn't distinguish between an unknown issuer, an invalid signature, an expired token, an inactive trust policy, and a claim set that matches no mapping or more than one. If you get a 401, check the validation rules and confirm your trust policy and service user mapping against the claims your identity provider actually issues.

Example — Azure Entra managed identity

This example gives an Azure virtual machine read access to logs in a Dynatrace environment. The virtual machine uses a managed identity, so no secret is stored anywhere.

Step 1—Expose an audience in Microsoft Entra

Register an application in Microsoft Entra ID and set its Application ID URI, for example api://dynatrace-wif. This value becomes the aud claim of the tokens your workloads request, and the audience of the Dynatrace trust policy. You don't need a client secret.

Note your Entra tenant ID and the object ID of the managed identity assigned to the virtual machine.

Step 2—Create the trust policy

The issuer URL for Microsoft Entra tokens is https://sts.windows.net/{tenantId}/, including the trailing slash.

curl --request POST \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies' \
--header 'Authorization: Bearer {your-access-token}' \
--header 'Content-Type: application/json' \
--data '{
"name": "Azure production tenant",
"issuerUrl": "https://sts.windows.net/{tenantId}/",
"audience": "api://dynatrace-wif"
}'

Note the uuid from the response.

Step 3—Map the managed identity to a service user

Match on the sub claim, which for a managed identity is the object ID of its service principal.

curl --request POST \
--url 'https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/wif/trust-policies/{trustPolicyUuid}/mappings' \
--header 'Authorization: Bearer {your-access-token}' \
--header 'Content-Type: application/json' \
--data '{
"serviceUserUuid": "be820735-3114-4d40-9c44-dfa18fa62be9",
"environmentId": "{environmentId}",
"scopes": ["storage:logs:read"],
"claimMappings": [
{
"claimName": "sub",
"claimValue": "{managed-identity-object-id}",
"matchType": "EXACT"
}
]
}'

Step 4—Get an Entra token on the workload

From inside the virtual machine, request a token from the Azure Instance Metadata Service for the audience you exposed in Step 1.

AZURE_TOKEN=$(curl -s \
--header 'Metadata: true' \
--url 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://dynatrace-wif' \
| jq -r '.access_token')

Step 5—Call the Dynatrace API

Pass the Entra token straight to Dynatrace. The request runs as the mapped service user.

curl --request POST \
--url 'https://{environmentId}.apps.dynatrace.com/platform/storage/query/v1/query:execute' \
--header "Authorization: Bearer $AZURE_TOKEN" \
--header 'Content-Type: application/json' \
--data '{ "query": "fetch logs | limit 10" }'

Repeat Step 4 whenever the Entra token expires.

Validation rules

For the maximum number of trust policies per account and service user mappings per trust policy, see Identity and Access Management limits.

RuleDetail

Trust policy uniqueness

One trust policy per combination of account, issuer URL, and audience.

Issuer URL

HTTPS only, up to 2048 characters, and no query string or fragment. Dynatrace rejects URLs that resolve to private or loopback addresses.

Audience

Non-empty, up to 512 characters. Compared exactly: the trust policy audience must equal one value in the external token's aud claim. There is no wildcard matching and no normalization.

Signature algorithms

ES256 and RS256.

Required token claims

iss, aud, exp, iat, and sub.

Claim mappings

At least one per service user mapping. Each claimName may appear only once, and all must match.

Related topics

  • Tokens and oauth client concepts
  • Service users
  • Platform tokens
  • OAuth clients
Related tags
Dynatrace Platform