Access Azure Storage from GitHub Actions with a Managed Identity
In this tutorial, we will use OpenID Connect (OIDC) to authenticate GitHub Actions to an Azure Storage Account without storing long-lived client secrets. By establishing a federated credential, GitHub dynamically requests short-lived access tokens from Microsoft Entra ID by proving the workflow originates from a trusted repository, branch, or environment.
1. Create a User-Assigned Managed Identity
Since GitHub Actions runs outside of Azure, you must create a User-Assigned Managed Identity.
Once created, assign the identity an Azure RBAC role on your target Storage Account or container:
- Storage Blob Data Contributor for read/write access.
- Storage Blob Data Reader for read-only access.
2. Add the federated credential
In Microsoft Entra ID, navigate to your managed identity and create a federated identity credential. Use the following configuration:
- Issuer:
https://token.actions.githubusercontent.com - Audience:
api://AzureADTokenExchange - Subject:
repo:YOUR_ORG/YOUR_REPO:ref:refs/heads/main
If using GitHub environments, scope the credential by modifying the subject to repo:YOUR_ORG/YOUR_REPO:environment:prod.
3. Store Azure Identifiers in GitHub
You do not need to store sensitive passwords or client secrets. However, the workflow still needs to know which identity and tenant to target. Add these values as GitHub Repository Variables:
AZURE_CLIENT_ID, which is the Client ID of your User-Assigned Managed IdentityAZURE_TENANT_IDAZURE_SUBSCRIPTION_ID
All three ids can be found in Azure Portal.
4. Configure the GitHub workflow
Your workflow requires the id-token: write permission to request the OIDC token from Entra ID.
name: Upload to Azure Storage
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to Azure with OIDC
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Upload file to Blob Storage
run: |
az storage blob upload \
--account-name mystorageaccount \
--container-name mycontainer \
--name hello.txt \
--data "Hello from GitHub Actions" \
--auth-mode login
Best Practices
- Apply the principle of least privilege: For example, in our Storage Account case, scope the RBAC role directly to the specific storage container rather than the entire storage account or subscription.
- Use Environment Protection: Tie federated credentials to specific GitHub Environments (e.g., prod) rather than branches, so you can enforce manual approval gates before deployment.
- If authentication fails, the most common error is a typo in the Subject claim, for example mismatching the branch name.