Why
When integrating Azure DevOps (ADO) with Pulumi Cloud, a common approach is to generate a Pulumi access token and store it as a secret in your pipeline. While this works, it introduces another credential that must be managed, rotated, and protected.
Modern CI/CD platforms are increasingly moving away from long-lived secrets in favor of federated identity. ADO supports OpenID Connect (OIDC), allowing pipelines to request short-lived identity tokens at runtime. Instead of storing a Pulumi access token, ADO can prove its identity directly to Pulumi Cloud and receive temporary credentials for the duration of the pipeline run.
This approach provides several benefits:
- Eliminates long-lived Pulumi access tokens from your pipeline
- Reduces the risk of credential leakage
- Uses short-lived credentials that automatically expire
- Simplifies credential management across multiple projects
- Follows modern cloud authentication best practices
In this article, we'll configure a trust relationship between ADO and Pulumi Cloud so that pipeline executions can be authenticated without storing any Pulumi secrets.
For simplicity, I'm going to assume you already have a federated service connection in ADO that authenticates with Azure.
How
Here's how we will do this:
- Configure Pulumi Cloud to trust ADO
- Set a policy in Pulumi to validate the token
- Request an OIDC token in the pipeline
- Log in with this token to Pulumi
- Finally, run Pulumi commands in the pipeline
Before we start clicking through configuration screens, it helps to understand the authentication flow. Azure DevOps already has a trust relationship with Azure through the federated service connection. We'll leverage that same identity to authenticate to Pulumi Cloud without storing a Pulumi access token.
---
config:
theme: 'dark'
---
flowchart LR
AZ[Azure]
ADO[Azure DevOps Pipeline]
PC[Pulumi Cloud]
AZ -->|Federated Service Connection| ADO
ADO -->|OIDC Token| PC
ADO -->|pulumi up| AZ
PC -->|Authentication & State| ADO
What
Take a look at the service connection in Azure DevOps that you use to connect to Azure (Azure Resource Manager).

You can do that by going to the project settings [1] and "Service connections" [2].
The "Issuer" (iss) and "Subject identifier" sub will be listed here [3]. These details, along with the "Audience" (aud) can also be on the App Registration [4] itself under the "Certificates & secrets" blade and the "Federated credentials" tab.
fb60f99c-7a34-4190-8149-302f77469936 which is a magic GUID for api://AzureADTokenExchangeArmed with this information, we can head over to the Pulumi Cloud platform and set up the OIDC issuer.

- Make sure that you have the correct organization or user selected
- Select Settings
- Select "Access management"

On the access management screen:
- Select "OIDC Issuers"
- Then "Register issuer"

The name field can be whatever makes sense to your organization and is for display purposes only.
The URL field is the Issuer from ADO.
You can read more about the "Max expiration" and "Thumbprints" from Pulumi's documentation.
Next, we need to set the access policies. These policies are how Pulumi and ADO will agree that the token is valid.

Set the "Decision" to "Allow". Select the appropriate "Token type". Set "Role" to "Member".
For the rules, I added two. One was aud for the audience and iss for the issuer. You could probably add more like the sub , but these two will most likely be sufficient.
Save the policy, and we're done with Pulumi Cloud for now.
Pipelines
Now we can turn our attention to the pipelines.
Create a pipeline with an AzureCLI task. Make sure to enable access to the service principal. In YAML you can do this with addSpnToEnvironment: true under the inputs. In the classic release pipelines, you find that under "Advanced" and check the box labeled "Access service principal details in script".
If you plan to use Pulumi's CLI directly, then this script shows how to log in to Pulumi Cloud.
Note that there's also --oidc-team, --oidc-user instead of --oidc-org.
steps:
- task: AzureCLI@2
displayName: 'Pulumi Cloud OIDC'
inputs:
azureSubscription: 'Pulumi-ADO-Federated-Connection'
scriptType: pscore
scriptLocation: inlineScript
addSpnToEnvironment: true
visibleAzLogin: false
inlineScript: |
$idToken = $env:idToken
Write-Host "OIDC token returned. Setting output var 'PulumiOidcToken'"
Write-Host "##vso[task.setvariable variable=PulumiOidcToken]$idToken"
Write-Host "Attempting login"
pulumi login --oidc-token $idToken --oidc-org JpPulumiBlog
Write-Host "Who Am I?"
pulumi whoami
Write-Host "Listing projects"
pulumi project ls
Pulumi also has a dedicated ADO task that you can use. To use it, you need to set the token as an output variable and provide that variable to the "Login Args". That looks something like this:
steps:
- task: AzureCLI@2
displayName: 'Pulumi Cloud OIDC'
inputs:
azureSubscription: 'Pulumi-ADO-Federated-Connection'
scriptType: pscore
scriptLocation: inlineScript
addSpnToEnvironment: true
visibleAzLogin: false
inlineScript: |
$idToken = $env:idToken
Write-Host "OIDC token returned. Setting output var 'PulumiOidcToken'"
# This sets a pipeline variable called 'PulumiOidcToken' to the toke value
Write-Host "##vso[task.setvariable variable=PulumiOidcToken]$idToken"
- task: pulumi.build-and-release-task.custom-build-release-task.Pulumi@1
displayName: 'Run pulumi preview'
inputs:
azureSubscription: 'Pulumi-ADO-Federated-Connection'
command: preview
loginArgs: '--oidc-token $(PulumiOidcToken) --oidc-org JpPulumiBlog'
stack: dev
# You will need to point this to the folder where your Pulumi.yaml file lives
cwd: '[path to Pulumi project]'
Troubleshooting
If authentication isn't working as expected, there are a few common areas to check.
Token exchange failed
If you get an error OIDC token exchange failed: Post "/api/oauth/token": unsupported protocol scheme "" then you may need to explicitly set the backend with pulumi login https://api.pulumi.com ....
Invalid issuer
If Pulumi reports that the issuer is invalid or untrusted, verify that the issuer URL configured in Pulumi Cloud exactly matches the iss claim in the token issued by ADO.
Audience mismatch
If authentication fails due to an audience validation error, inspect the aud claim in the token and compare it with the value used in your Pulumi access policy. In many Azure DevOps federated credential configurations, the audience defaults to api://AzureADTokenExchange (represented internally by the GUID fb60f99c-7a34-4190-8149-302f77469936).
Access denied
If the token is accepted but access is denied, review the access policy associated with the OIDC issuer. Ensure the policy decision is set to Allow, the correct token type is selected, and the assigned role has sufficient permissions for the actions being performed.
Inspecting the token
When troubleshooting, it can be helpful to inspect the token claims directly. You can decode the JWT and verify the values for:
iss(Issuer)aud(Audience)sub(Subject)
These values should match the expectations configured in your Pulumi OIDC issuer and access policies.
Verify authentication independently
Before running pulumi up, consider validating authentication separately pulumi whoami.
If this command succeeds, Pulumi has successfully authenticated and any subsequent issues are likely related to your stack configuration or Azure permissions rather than OIDC authentication.
Conclusion
By configuring Pulumi Cloud to trust ADO through OIDC, we've eliminated the need to store a long-lived Pulumi access token in our pipeline. Instead, ADO can obtain a short-lived identity token from the existing federated service connection and use that token to authenticate with Pulumi Cloud at runtime.
While the initial setup requires a little more configuration than generating an access token, the result is a more secure and maintainable authentication model. There are no Pulumi secrets to rotate, no credentials to distribute across projects, and a significantly lower risk of a leaked token granting long-term access.
