Back to overview
Security

Stop Putting Secrets in Your CI/CD Pipelines: Workload Identity Federation with Azure DevOps

Stop Putting Secrets in Your CI/CD Pipelines: Workload Identity Federation with Azure DevOps

How Pipeline Secrets Become Liabilities

CI/CD pipelines regularly connect to cloud resources to deploy applications, push container images, and fetch runtime configuration. For a long time, the standard pattern relied on creating an Azure service principal, generating a client secret, and saving that secret as a pipeline variable.

Static secrets spread quickly across pipeline settings, build logs, and repository histories. They rarely expire quickly, and they lack binding to the specific pipeline job using them. When a credential leaks, detection often happens only after unauthorized activity has already occurred.

Workload Identity Federation removes stored secrets from the workflow. Instead of long-lived credentials, Azure Entra ID directly validates the pipeline during execution using OpenID Connect (OIDC).

How Pipeline Authentication Has Evolved

Stage 1: Username and Password

The earliest pattern relied on shared service accounts with static credentials stored in pipeline variables. Password rotations caused unexpected pipeline failures, auditing was difficult across teams, and a single compromised build agent risked exposing all systems accessible to that account.

Stage 2: Client Secrets and App Registrations

Azure App Registrations improved access control by binding permissions to a specific application identity. However, they still require generating and storing long-lived secrets in pipeline variables (such as AZURE_CLIENT_SECRET). These secrets require scheduled rotations, and Azure cannot verify whether the caller presenting the secret is your build agent or an attacker.

Stage 3: Azure Key Vault for Bootstrap Secrets

Fetching secrets from Azure Key Vault at runtime centralized secret storage and audit logging. However, the pipeline still needs an initial credential to access Key Vault in the first place, leaving the bootstrap secret problem unresolved.

Stage 4: Workload Identity Federation

Workload Identity Federation replaces static credentials with short-lived OIDC tokens generated by Azure DevOps for each job. Azure Entra ID validates the token against a configured trust relationship. If the organization, project, and pipeline match the federated credential, Entra ID grants access for that specific run.

Approach Stored Secret? Rotation Required? Pipeline-Bound?
Username/Password Yes Manual No
Client Secret Yes Manual (1-2 years) No
Key Vault + Bootstrap Secret Yes Partially automated No
Workload Identity Federation No None Yes

How the OIDC Exchange Works

Azure DevOps acts as an OpenID Connect identity provider, signing JSON Web Tokens (JWTs) for active pipeline jobs. Azure Entra ID evaluates these tokens against configured trust policies:

  1. The pipeline job starts on an Azure DevOps agent.
  2. Azure DevOps generates a short-lived OIDC token signed with its private key.
  3. The pipeline task sends this token to the Azure Entra ID token endpoint.
  4. Entra ID validates the signature against Azure DevOps public keys fetched via the OIDC discovery document.
  5. Entra ID verifies that the token claims (issuer, subject, and audience) match the configured Federated Credential.
  6. Entra ID issues a short-lived Azure access token scoped to the app registration.
  7. The pipeline uses this access token to execute Azure CLI commands or resource deployments.

Because tokens expire within minutes and reflect exact pipeline run details, they cannot be reused outside their intended run context or replayed later.

Configuring Workload Identity Federation

You can set up federation manually through the Azure Portal and Azure DevOps, or let Azure DevOps configure resources automatically when creating a service connection. The manual steps show how each component connects.

Step 1: Register the Application in Entra ID

In the Azure Portal, go to Microsoft Entra ID → App registrations → New registration. Name the registration (such as sp-azuredevops-myproject) and leave the redirect URI blank.

Save the Application (client) ID and Directory (tenant) ID for the service connection setup.

Step 2: Assign Azure Roles

Grant the application registration permissions on your target subscription, resource group, or resource via Access Control (IAM) → Add role assignment.

Apply least privilege by scoping permissions tightly. For App Service deployments, assign Contributor at the resource group level. For Azure Container Registry, assign AcrPush directly to the registry.

# Assign a scoped role with Azure CLI
az role assignment create \
  --assignee "<your-app-registration-object-id>" \
  --role "Contributor" \
  --scope "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>"

Step 3: Add a Federated Credential

Under your App Registration, open Certificates & secrets → Federated credentials → Add credential.

Select Azure DevOps as the scenario and specify:

  • Organization: Your Azure DevOps organization name (e.g., mycompany)
  • Project: Your Azure DevOps project name
  • Entity type: Branch, Tag, Pull Request, or Environment
  • Ref: The Git ref (such as main or refs/heads/main)

Entra ID uses these fields to construct the expected subject claim. A credential targeting mycompany/myproject/refs/heads/main only accepts requests initiated by the main branch of that specific project.

// Example federated credential configuration
{
  "name": "azuredevops-myproject-main",
  "issuer": "https://vstoken.dev.azure.com/<organization-id>",
  "subject": "sc://mycompany/myproject/main",
  "audiences": ["api://AzureADTokenExchange"]
}

Step 4: Create the Service Connection

In Azure DevOps, open Project Settings → Service connections → New service connection → Azure Resource Manager.

Select Workload Identity Federation (manual) and provide:

  • Subscription ID and Subscription Name
  • Service Principal Id: The Application (client) ID from Step 1
  • Tenant ID: Your Directory (tenant) ID

When you save, Azure DevOps sends a test token request to Entra ID to verify that the federated credential matches.

Automatic Configuration Option

Selecting Workload Identity Federation (automatic) in Azure DevOps creates the App Registration, assigns roles, and configures the federated credential directly if your account has sufficient Azure permissions.

Pipeline Examples

Deploying to Azure App Service

trigger:
  - main

pool:
  vmImage: ubuntu-latest

variables:
  azureSubscription: 'my-workload-identity-connection'
  resourceGroupName: 'rg-myapp-prod'
  appName: 'app-myapp-prod'

stages:
  - stage: Deploy
    jobs:
      - job: DeployToAzure
        steps:
          - task: AzureCLI@2
            displayName: 'Deploy to Azure App Service'
            inputs:
              azureSubscription: $(azureSubscription)
              scriptType: bash
              scriptLocation: inlineScript
              inlineScript: |
                az webapp deployment source config-zip \
                  --resource-group $(resourceGroupName) \
                  --name $(appName) \
                  --src ./app.zip

The AzureCLI@2 task requests the OIDC token, exchanges it for an Entra ID token, and authenticates the CLI session without reading static secret variables.

Building and Pushing Container Images

trigger:
  branches:
    include:
      - main
      - 'release/*'

pool:
  vmImage: ubuntu-latest

variables:
  azureSubscription: 'my-workload-identity-connection'
  containerRegistry: 'mycompanyacr.azurecr.io'
  imageRepository: 'myapp'
  tag: $(Build.BuildId)

stages:
  - stage: BuildAndPush
    jobs:
      - job: Build
        steps:
          - task: Docker@2
            displayName: 'Build and push image'
            inputs:
              command: buildAndPush
              repository: $(imageRepository)
              dockerfile: '**/Dockerfile'
              containerRegistry: $(azureSubscription)
              tags: |
                $(tag)
                latest

Reading Key Vault Secrets Without Bootstrap Credentials

Federated service connections authenticate directly to Azure Key Vault, removing the need for an initial bootstrap secret in the pipeline.

steps:
  - task: AzureKeyVault@2
    displayName: 'Fetch application secrets'
    inputs:
      azureSubscription: 'my-workload-identity-connection'
      KeyVaultName: 'kv-myapp-prod'
      SecretsFilter: 'DatabaseConnectionString,ApiKey,SigningCertificate'
      RunAsPreJob: true

  - script: |
      echo "Deploying with fetched configuration..."
    displayName: 'Deploy application'

Restricting Deployments with Environment Checks

Federated credentials can bind directly to Azure DevOps Environments. This ensures only pipeline executions targeting a specific environment (and satisfying its approvals or verification checks) can acquire production tokens.

stages:
  - stage: Production
    jobs:
      - deployment: DeployProduction
        environment: production
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureCLI@2
                  displayName: 'Deploy to Production'
                  inputs:
                    azureSubscription: 'production-workload-identity-connection'
                    scriptType: bash
                    scriptLocation: inlineScript
                    inlineScript: |
                      az webapp deploy --resource-group rg-prod \
                        --name app-prod --src-path ./release.zip

Because the credential expects the production environment claim, runs triggered outside that target fail token validation at the Entra ID boundary.

Operational and Security Practices

Narrow Credential Scopes

Create distinct federated credentials for different branches and environments:

  • main branch for production releases
  • refs/heads/release/* for staging deployments
  • Pull requests for read-only validation jobs
  • production environment for gated release stages

Separate App Registrations Across Environments

Use distinct App Registrations for development, staging, and production. Isolating identities prevents misconfigurations in lower environments from impacting production resources.

Enforce Least Privilege

Scope role assignments to specific resource groups or resources rather than entire subscriptions.

# Resource-scoped assignment
az role assignment create \
  --assignee "<object-id>" \
  --role "AcrPush" \
  --scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ContainerRegistry/registries/<acr-name>"

Review Sign-in Logs

Workload identity authentications appear under Microsoft Entra ID → Sign-in logs → Service principal sign-ins. Send these logs to Azure Monitor or your SIEM to track which pipelines request tokens and observe failures.

Converting Existing Service Connections

Existing Azure DevOps service connections using secret keys can be updated in place:

  1. Open Project Settings → Service connections.
  2. Select your service connection and click Edit.
  3. For connections created with the automatic option, click Convert to switch authentication to workload identity federation.
  4. For manual connections, add the federated credential in Entra ID and update the connection type in Azure DevOps settings.

Converting existing connections preserves existing role assignments while removing secret expiration alerts and rotation schedules.

Troubleshooting Common Errors

AADSTS70021: No matching federated identity record found

The subject claim in the pipeline token does not match any federated credential on the App Registration. Verify that:

  • The organization name matches your Azure DevOps organization casing exactly.
  • The project name matches.
  • The entity type and ref match the Git reference format (such as refs/heads/main versus main).

AADSTS700016: Application not found in directory

The Application (client) ID in the service connection does not match an App Registration in the targeted Entra ID tenant.

AuthorizationFailed: Client does not have authorization

The token exchange succeeded, but the App Registration lacks the required Azure RBAC role on the target resource. Check role assignments and allow a few minutes for new assignments to propagate.

Service Connection Verification Fails During Setup

Check the issuer URL format in your federated credential. Azure DevOps expects https://vstoken.dev.azure.com/<organization-id>, where the organization ID is the GUID found under Organization Settings → Overview.

#Azure DevOps #Security #Workload Identity Federation #Azure #CI/CD #Entra ID