Deploy to Azure with no stored secrets: OIDC pipelines explained
For years, connecting a pipeline to Azure meant pasting a client secret into your CI settings and praying it never leaked. Workload identity federation ends that: your workflow trades a short-lived token for an Azure token at run time — with nothing to store, rotate, or steal.
Think about the old way for a second, because its badness is the whole motivation. To let GitHub Actions deploy to Azure, you created a service principal with a client secret, then pasted that secret into your repository's CI settings. That secret was a long-lived password to your cloud, sitting in a system outside Azure, that you had to remember to rotate and hope nobody exfiltrated. It was the single most valuable thing in your pipeline and the easiest thing to leak. Workload identity federation deletes that whole risk category.
The idea: trust, not secrets
Microsoft's definition is the thesis of the article: workload identity federation "enables secure access to Microsoft Entra protected resources without managing secrets." Instead of storing a credential, you configure a trust relationship: you tell an app registration or a user-assigned managed identity in Entra ID to trust tokens from an external identity provider — for a pipeline, that provider is GitHub. Once that trust exists, the workflow exchanges a token GitHub already issues it for a real Azure access token, on demand.
The thing that authorises your deploy is no longer a secret you hold. It is a relationship you declared — "I trust tokens that come from this specific repo, on this specific branch." There is nothing to paste anywhere.
How the exchange works
The flow is short, and it happens fresh on every run:
- The GitHub Actions workflow asks GitHub (the external identity provider) for a token.
- GitHub issues a short-lived OIDC token that describes the run — which repo, which branch, which environment.
- The workflow's sign-in step sends that token to the Microsoft identity platform and asks for an access token.
- Azure checks the token against the trust you configured, validating it against GitHub's OpenID Connect issuer.
- If the trust matches, Azure issues a short-lived access token.
- The workflow uses that token to deploy — publish the web app, run the Bicep, whatever it does.
Every token in that chain is short-lived and scoped to one run. Nothing durable is stored on GitHub's side. The pipeline authenticates like a badge-in at the door rather than carrying a copied key in its pocket.
The Azure login step declares three IDs — no secret in sight — and the whole thing hinges on one permission line that lets the job request an OIDC token:
# the permission that makes OIDC possible permissions: id-token: write contents: read # sign in with IDs only — never a secret - uses: azure/login@v2 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
Those three values are identifiers, not credentials — knowing them grants nothing without the federated trust. There is no client-secret line, and that absence is the entire point.
The goal is a pipeline where gh secret list returns nothing — because there is no secret to list.
Why this is the standard now
Set aside the elegance and count the risks it removes. There is no long-lived secret to leak. Nothing to rotate on a calendar, and no 2 a.m. outage when a certificate silently expires. The trust is scoped tightly — you can require that only your main branch, or only a protected environment, may assume the identity — so a fork or a random branch cannot deploy. It is the same idea managed identities use inside Azure, now extended to a workload running outside it. That is why federated OIDC is the modern default for cloud pipelines, and why "how does your pipeline authenticate to Azure?" is a live interview question.
If your honest answer is still "a client secret in CI," this is the upgrade worth making first — not because secrets always leak, but because the best credential, as ever, is the one that does not exist to be stolen.