GitHub Actions OIDC to Azure
Use GitHub OIDC for Azure deployments from GitHub Actions. Do not store Azure client secrets in GitHub secrets for new projects.
Required Azure objects
- Microsoft Entra app registration or user-assigned managed identity.
- Federated credential trusting GitHub's OIDC issuer.
- Azure RBAC assignments at the smallest practical deployment scope.
Recommended subject patterns
Use the tightest subject that matches the workflow path.
| Scenario | Example subject |
|---|---|
| Protected production environment | repo:<org>/<repo>:environment:Production |
| Specific branch | repo:<org>/<repo>:ref:refs/heads/main |
| Pull request validation | repo:<org>/<repo>:pull_request |
Prefer GitHub environments with reviewers for production rather than broad branch-only trust.
Note: repositories created or renamed after 2026-07-15 default to an immutable subject format that embeds the owner and repository IDs, for example repo:OWNER@OWNER-ID/REPO@REPO-ID:ref:refs/heads/BRANCH (such as repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main); this does not apply to GitHub Enterprise Server. Federated credential subjects must match whichever format the repository actually issues, so read the token's sub claim rather than assuming the legacy repo:org/repo:... shape. If you use broad wildcard subjects (for example repo:<org>/<repo>:*) as an escape hatch, a repo created or renamed after 2026-07-15 silently stops matching; the immutable format never matches name-based wildcards. Prefer flexible federated credentials (see SKILL.md) over broad wildcards when pattern trust is genuinely needed.
Workflow pattern
name: deploy
on:
workflow_dispatch:
push:
branches: [main]
permissions:
id-token: write
contents: read
env:
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
jobs:
deploy:
runs-on: ubuntu-latest
environment: Production
steps:
- uses: actions/checkout@v4
- name: Azure login with OIDC
uses: azure/login@v3
with:
client-id: ${{ env.AZURE_CLIENT_ID }}
tenant-id: ${{ env.AZURE_TENANT_ID }}
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
- name: Deploy Bicep
run: |
az deployment group create \
--resource-group "$RESOURCE_GROUP_NAME" \
--template-file infra/main.bicepGuardrails
- Never add
AZURE_CLIENT_SECRETfor new workflows. - Avoid broad
repo:<org>/<repo>:*subjects. - Keep production credentials tied to a protected environment.
- Create separate federated credentials for prod/non-prod.
- Restrict Azure RBAC scope to the deployment target.
- For reusable workflows, consider whether the federated credential should trust the caller workflow, the called workflow, or both based on the token claims your organization uses.
Verification
az account show --query "{tenantId:tenantId, subscriptionId:id, user:user.name}" -o json
az role assignment list --assignee "$AZURE_CLIENT_ID" --all -o tableIf token exchange fails, compare issuer, subject, and audience with the federated credential and check whether repository/environment/branch names changed.