CI/CD runners and automation often authenticate with long-lived secrets. Workload Identity Federation replaces those secrets with short-lived OIDC tokens.
Preface
Automated workloads historically used service principals with client secrets or certificates that must be stored and rotated across external systems.
That model spreads the most sensitive credentials in the environment across pipeline variables, secret stores, developer laptops and third-party platforms. Every copy is a place where the credential can leak, and every rotation is a coordinated change across systems that different teams own. Federation removes the stored secret from the equation and replaces it with a trust relationship that Entra ID can evaluate on every request.
Problem
Static secrets are frequently leaked from repositories, config files, and unrotated environments. Compromise grants the full permissions of the service principal without MFA.
Deployment identities are usually among the most privileged in the tenant: they create resources, assign roles and write to production subscriptions. A secret copied once into a pipeline tends to stay valid for its full lifetime, and if it is exfiltrated, it can be used from anywhere until someone notices and revokes it.
How Federation Works
With workload identity federation, the external platform becomes an identity provider for its own workloads. For a GitHub Actions job, the flow is:
- The job requests an OIDC token from the GitHub token service. The token is signed by GitHub and contains claims such as the issuer, the subject (repository plus branch, environment or pull request context) and the audience.
- The job presents that token to Entra ID as a client assertion for a specific application or user-assigned managed identity.
- Entra ID validates the signature against the issuer's published keys, then checks that the issuer, subject and audience exactly match a federated credential configured on that identity.
- If they match, Entra ID issues a short-lived access token. Nothing long-lived is stored in the pipeline.
The same model applies to Kubernetes service accounts (including AKS workload identity), Azure DevOps service connections, Google Cloud, AWS and any other compliant OIDC issuer.
The security of the whole arrangement now rests on two things: how narrowly the subject is defined, and who can influence what runs under that subject.
Solution
Align with the zero trust principles of NIST SP 800-207, where every request is authenticated and authorized against explicit policy:
- Implement federated credentials scoped to repository, environment, or subject.
- Exchange ephemeral OIDC tokens for short-lived Entra access tokens at runtime.
- Continuously audit which repositories and subjects are trusted.
Design Guidance
Scope subjects as narrowly as the workflow allows
For GitHub, a subject such as repo:org/repo:environment:production is usually better than a branch reference, because GitHub environments can require reviewers and restrict which branches may deploy. Avoid trusting pull request subjects for any identity that can write to production; pull requests can come from contributors who should not be able to deploy.
One identity per trust boundary
Use separate applications or managed identities for development, staging and production, each with its own role assignments. A single identity trusted by several repositories means the weakest repository defines the security of all of them.
Protect the source platform
Federation moves trust to the CI/CD platform. Branch protection, required reviews, restrictions on who can edit workflow files, pinned third-party actions and environment protection rules are now part of your identity security. Anyone who can change what runs on a trusted branch can obtain a token.
Least privilege at the resource
Federation removes the secret; it does not reduce permissions. Assign Azure RBAC roles at resource group scope rather than subscription scope where possible, and avoid granting directory roles or tenant-wide Graph permissions to deployment identities.
Implementation Steps
- Inventory pipelines and automation that use client secrets or certificates, and map each to the identity and permissions it uses.
- Create or choose the identity: an app registration, or a user-assigned managed identity if the workload only needs Azure resources.
- Add the federated credential with the exact issuer, subject and audience for that workflow.
- Update the pipeline to request an OIDC token (in GitHub Actions, the
id-token: writepermission) and sign in with the client ID and tenant ID only. - Test, then remove the old secret from both the pipeline and the application object. A migration that leaves the secret in place has not reduced risk.
- Record ownership of every federated credential so reviews have someone to ask.
PowerShell: Create a Federated Credential
Connect-MgGraph -Scopes "Application.ReadWrite.All"
$params = @{
Name = "GitHubActionsFederatedCredential"
Issuer = "https://token.actions.githubusercontent.com"
Subject = "repo:enterprise/repo:ref:refs/heads/main"
Description = "Federated trust for main branch deployments"
Audiences = @("api://AzureADTokenExchange")
}
# -ApplicationId takes the application's object ID, not its client (app) ID
New-MgApplicationFederatedIdentityCredential -ApplicationId "<application-object-id>" -BodyParameter $params
To review existing trust, list federated credentials for every application with Get-MgApplicationFederatedIdentityCredential and flag broad subjects, unexpected issuers and any credential without a documented owner.
Monitoring and Detection
Adding or changing a federated credential is as sensitive as adding a secret: it grants a new external party the ability to obtain tokens. Alert on it in Sentinel:
AuditLogs
| where OperationName == "Update application"
| where tostring(TargetResources[0].modifiedProperties) has "FederatedIdentityCredentials"
| extend Actor = coalesce(tostring(InitiatedBy.user.userPrincipalName),
tostring(InitiatedBy.app.displayName))
| project TimeGenerated, Actor, App = tostring(TargetResources[0].displayName), Result
Also baseline federated sign-ins in AADServicePrincipalSignInLogs (the ClientCredentialType column distinguishes federated credentials from secrets and certificates) and investigate sign-ins to sensitive resources at unusual times. Continue to alert on any new client secret added to an application you have already migrated.
Common Mistakes
- Leaving the old client secret active "just in case" after the migration.
- Overly broad subjects, or trusting pull request contexts for production identities.
- Sharing one identity across many repositories or environments.
- Ignoring workflow file permissions, so anyone with write access to the repository can change what the trusted job does.
- Assuming federation fixes over-privilege. It removes the stored credential; role assignments still need review.
Where to Start
Pick the single most privileged deployment pipeline that still uses a client secret. Move it to a federated credential scoped to a protected production environment, remove the secret, and add the audit alert above. Then repeat across the remaining pipelines, prioritized by the permissions each identity holds.