Configuring azure oidc for github actions
Skill alexpizarro/azure-lean-stack-skills/skills/configuring-azure-oidc-for-github-actions
Azure apps that cost nothing when nobody's using them. A Claude Code skill pack — 14 composable skills, 37+ documented gotchas, branch-per-env CI/CD.
npx -y skills add alexpizarro/azure-lean-stack-skills --skill configuring-azure-oidc-for-github-actionsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Sets up branch-scoped Azure OIDC for GitHub Actions — creates service principals, federated credentials bound to each branch's refs/heads/{branch}, generates SQL passwords, and sets GitHub secrets. Use when bootstrapping CI/CD, onboarding a new environment branch, or fixing AADSTS70021 subject mismatches.
SKILL.md
5.2 KB, as published. Nobody here has run it
Configuring Azure OIDC for GitHub Actions
Sets up secret-less Azure authentication for GitHub Actions. After running these scripts, deploys work via git push with no client secrets to rotate.
Why OIDC
- No client secrets stored in GitHub or in code
- Branch-scoped: the
testSP can't deploy toproductionand vice versa - Federated credential subject is bound to
refs/heads/{branch}exactly — drift causesAADSTS70021
Workflow checklist
Copy this checklist into your response and check items off as you complete them:
OIDC + GitHub Actions setup:
- [ ] Step 1: Verify az login + gh auth login (and AZURE_CONFIG_DIR if shared machine)
- [ ] Step 2: Confirm GitHub repo exists, branches (test, production) created
- [ ] Step 3: Run scripts/create-sp-with-oidc.sh → creates test + prod SPs + federated creds
- [ ] Step 4: Run scripts/generate-sql-password.sh → SQL admin passwords
- [ ] Step 5: Run scripts/add-github-secrets.sh → 6 secrets set
- [ ] Step 6: For each additional environment branch (acme-demo, customer-uat, etc.), re-run with that branch name
- [ ] Step 7: First push to test → verify the workflow authenticates (no AADSTS70021)
Setup sequence
Run these scripts in order. Each is idempotent (safe to re-run).
# Prerequisites: az login and gh auth login already done.
# Use AZURE_CONFIG_DIR if this project shares a machine with others.
export ORG="acme" # short org prefix
export PROJECT="taskapp" # short project name
export GITHUB_ORG="myorg"
export REPO="taskapp"
# 1. Create both service principals + federated credentials
bash scripts/create-sp-with-oidc.sh
# 2. Generate SQL admin passwords (test + prod)
bash scripts/generate-sql-password.sh
# 3. Set the 6 GitHub secrets
bash scripts/add-github-secrets.sh
Each script prints what it will do, what already exists, and what was created.
The 6 GitHub secrets
| Secret | Scope | Source |
|---|---|---|
AZURE_TENANT_ID | Both envs | az account show --query tenantId |
AZURE_SUBSCRIPTION_ID | Both envs | az account show --query id |
AZURE_CLIENT_ID_TEST | Test SP appId | create-sp-with-oidc.sh output |
AZURE_CLIENT_ID_PROD | Prod SP appId | create-sp-with-oidc.sh output |
SQL_ADMIN_PASSWORD_TEST | Test SQL admin | generate-sql-password.sh output |
SQL_ADMIN_PASSWORD_PROD | Prod SQL admin | generate-sql-password.sh output |
Critical: federated credential subject format
Azure rejects the OIDC token if the subject doesn't match exactly:
repo:{GitHubOrg}/{Repo}:ref:refs/heads/{branch}
| Common mistake | Error |
|---|---|
ref:refs/heads/main when workflow pushes from test | AADSTS70021: No matching federated identity record found |
pull_request subject for branch push | same |
| Extra trailing slash | same |
Repo case mismatch (MyOrg vs myorg) | same |
If you see AADSTS70021, dump the actual issuer + subject from the failed run, then compare against the federated credential:
az ad app federated-credential list --id "$CLIENT_ID" -o table
When to use User Access Administrator
The default Contributor role is sufficient for SWA + SQL deploys. You need User Access Administrator when Bicep contains Microsoft.Authorization/roleAssignments resources — typically:
- FC1 Flex Consumption (Function App MI → Storage Blob Data Owner)
- Container Apps Jobs that need
AcrPullor similar - Any module that does
roleAssignmentson a child resource
Grant at the smallest scope that works (RG > subscription):
az role assignment create \
--assignee "$SP_OID" \
--role "User Access Administrator" \
--scope "/subscriptions/$SUB_ID/resourceGroups/$ORG-$PROJECT-rg-test"
If the RG doesn't exist yet, scope to subscription temporarily.
SP creation gotcha — WARNING in stdout
az ad sp create-for-rbac --sdk-auth prepends a WARNING: line to stdout. If that ends up in AZURE_CREDENTIALS (the legacy format), authentication silently fails. OIDC sidesteps this — we only store the appId, never the SP JSON. But if you ever need AZURE_CREDENTIALS, strip the warning:
az ad sp create-for-rbac ... --sdk-auth 2>/dev/null \
| python3 -c "import sys,json; d=sys.stdin.read(); print(json.dumps(json.loads(d[d.find('{'):]),indent=2))"
Composes with
- scaffolding-azure-bicep-infrastructure — generates the workflow files that consume these secrets
- Microsoft's
entra-app-registration— deeper app-registration mechanics - Microsoft's
azure-rbac— RBAC verification on running deployments
Reference
See references/federated-credentials.md for the full breakdown of subject formats, audiences, and verification commands.