GitHub Actions OIDC Authentication to Azure

If you’ve been following this series, you’ll know we’ve covered foundational GitHub Actions workflows and secrets management. Now it’s time to solve one of the most common questions I get when teams move from Azure DevOps to GitHub: how do you authenticate to Azure without storing credentials?

The answer is OIDC, and in this post I’ll show you exactly how to set it up.

I’ve already covered why federated identities are the right choice for non-Azure workloads and how to secure workload identities properly. I won’t repeat that here. What I want to do is get you from zero to a working OIDC connection in GitHub Actions, step by step.

Why OIDC and Not a Client Secret?

Quick version: client secrets are long-lived credentials. They get stored in pipelines, shared across teams, rotated inconsistently, and forgotten about. I’ve seen service principals with secrets that were two years old and nobody knew what they were authenticating.

With OIDC, GitHub Actions exposes an OpenID Connect identity token service that Entra ID can trust. When a workflow runs, GitHub issues a short-lived token for that specific run. Entra ID validates it against a federated identity credential you’ve configured on your service principal. No secret is stored anywhere. If the job ends, the token is gone.

It’s a fundamentally better model. The setup takes about 15 minutes.

What You’ll Need

  • An Azure subscription
  • Permissions to create app registrations in Entra ID and assign RBAC roles
  • A GitHub repository (organisation or personal)
  • The Azure CLI (or you can use the portal , I’ll show both where it matters)

Step 1: Create the App Registration

First, create the app registration in Entra ID. This is the identity GitHub will authenticate as.

az ad app create --display-name "github-actions-oidc-demo"

Note the appId from the output , you’ll need it.

Then create the service principal:

az ad sp create --id <appId>

Note the id (object ID) of the service principal from the output.

The app registration defines the identity itself.

The service principal is the security instance of that identity inside your tenant and is what receives RBAC permissions.

Step 2: Assign RBAC Permissions

Assign the service principal the role it needs on the target scope. For most CI/CD workflows deploying infrastructure, Contributor on the subscription is a common starting point , though you should scope this as tightly as your workload allows.

az role assignment create \
  --assignee <appId> \
  --role Contributor \
  --scope /subscriptions/<subscription-id>

If you’re deploying to a specific resource group, scope it there instead:

az role assignment create \
  --assignee <appId> \
  --role Contributor \
  --scope /subscriptions/<subscription-id>/resourceGroups/<resource-group-name>

Least privilege matters here. Don’t assign Contributor at subscription scope if the workflow only needs to write to one resource group.

Step 3: Configure the Azure Federated Identity Credential

This is the trust relationship that tells Entra ID: “trust tokens issued by GitHub Actions, for this specific repository and branch.”

az ad app federated-credential create \
  --id <appId> \
  --parameters '{
    "name": "github-actions-main",
    "issuer": "https://token.actions.githubusercontent.com",
    "subject": "repo:<org>/<repo>:ref:refs/heads/main",
    "description": "GitHub Actions OIDC for main branch",
    "audiences": ["api://AzureADTokenExchange"]
  }'

The subject claim is the important piece. It controls exactly what can authenticate using this credential. The format is:

repo:<org>/<repo>:<filter>

Common subject patterns:

Subject When it’s used
repo:org/repo:ref:refs/heads/main Pushes to main branch
repo:org/repo:environment:production Jobs targeting a GitHub Environment
repo:org/repo:pull_request Pull request workflows
repo:org/repo:ref:refs/tags/1.0.0 Tag-based releases

I’d recommend using environment-based subjects for anything touching production. It means you can add environment protection rules in GitHub and the credential only works when those rules pass.

Step 4: Add GitHub Repository Secrets

GitHub Actions needs three values to authenticate using OIDC:

  • AZURE_CLIENT_ID – the app registration’s appId
  • AZURE_TENANT_ID – your Entra ID tenant ID
  • AZURE_SUBSCRIPTION_ID – the subscription you’re deploying to

Go to Settings → Secrets and variables → Actions → New repository secret and add all three.

These aren’t credentials, they’re identifiers. The OIDC token is what authenticates; these values just tell the Azure login action where to exchange it.

Step 5: Configure the Workflow

Here’s a minimal workflow that authenticates to Azure using OIDC and lists resource groups to confirm the connection is working.

name: OIDC Auth Demo

on:
  push:
    branches:
      - main

permissions:
  id-token: write   # Required for OIDC token request
  contents: read

jobs:
  authenticate:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Azure Login (OIDC)
        uses: azure/login@v3
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Confirm access
        run: az group list --output table

The permissions block is critical. Without id-token: write, GitHub won’t issue the OIDC token and the login step will fail. This trips people up, if you get an authentication error and your credentials look right, check the permissions block first.

Step 6: Validate It Works

Push the workflow to main and watch the Actions run. If the Azure Login step succeeds and you see resource groups in the output, you’re done.

If it fails, the most common causes are:

  • Missing id-token: write permission
  • Subject mismatch – the workflow is running from a branch or environment that doesn’t match the federated credential subject
  • RBAC assignment propagation delay, Azure RBAC changes can take a few minutes to propagate

Using Environments for Production

If you’re deploying to production, I’d strongly recommend configuring this against a GitHub Environment rather than a branch.

Create a new federated identity credential with the environment subject:

az ad app federated-credential create \
  --id <appId> \
  --parameters '{
    "name": "github-actions-production",
    "issuer": "https://token.actions.githubusercontent.com",
    "subject": "repo:<org>/<repo>:environment:production",
    "description": "GitHub Actions OIDC for production environment",
    "audiences": ["api://AzureADTokenExchange"]
  }'

Then update the workflow job to target the environment:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Azure Login (OIDC)
        uses: azure/login@v3
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

Now you can add required reviewers and deployment protection rules to the production environment in GitHub. The OIDC credential only works when the job is running within that environment, so even if someone bypasses your branch protection, they can’t authenticate to Azure without the environment check passing.

Terraform with OIDC

If you’re using Terraform in your GitHub Actions workflow, you need to tell the AzureRM provider to use OIDC. The Azure login step sets the necessary environment variables, but you also need to set use_oidc = true in your provider block:

provider "azurerm" {
  features {}
  use_oidc        = true
  subscription_id = var.deployment_subscription_id
}

And the workflow step running Terraform needs the ARM environment variables populated:

- name: Terraform Plan
  env:
    ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
    ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
    ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
    ARM_USE_OIDC: true
  run: terraform plan

I cover this in more detail in the Terraform on Azure DevOps pipeline post,the same provider configuration applies here.

Summary

OIDC authentication for GitHub Actions is the right approach for authenticating to Azure. No secrets to rotate, no credentials to leak, and you can scope the trust precisely to the repositories, branches, and environments that need access.

The setup is straightforward once you understand the federated credential subject pattern. Get that right and everything else follows.

Next in the series, we’ll look at reusable workflows, and OIDC will be the foundation we build on from here.

Part of the GitHub Actions for Platform Engineers series.

Your subscription could not be saved. Please try again.
Your subscription to deniscooper.co.uk has been successful.

Join Cloud Engineers Who Build Smarter on Azure

Free checklists, practical guides and the latest posts — built for cloud engineers working with Azure and Terraform.

1 thought on “GitHub Actions OIDC Authentication to Azure”

  1. This article provides a clear and practical guide to implementing GitHub Actions with Azure OpenID Connect authentication. I particularly appreciate the focus on replacing long-lived credentials with short-lived OIDC tokens, which significantly improves the security of CI/CD pipelines while reducing the burden of secret management. It’s an excellent resource for DevOps engineers looking to adopt modern, credential-free authentication practices for Azure deployments.

    Reply

Leave a Comment