One Less Secret: Setting Up OIDC Between GitHub Actions and Azure

I'm not a fan of client secrets in GitHub Actions (and you shouldn’t be either). They expire at the worst times, they're attack vectors waiting to happen, and they have a funny way of expiring right when you’re doing your most important deployments.

The fix? Federated identities using OpenID Connect (OIDC). No secrets to store, no secrets to rotate, no secrets to leak.

This post walks you through setting it up between GitHub and Azure—step by step, with Terraform, of course.

How OIDC Authentication Actually Works

Before we dive into code, let's talk about what's happening under the hood. It's simpler than it sounds.

  1. Your GitHub Actions workflow starts running. GitHub generates a short-lived OIDC token that says "Hey, I'm a workflow running from your-org/your-repo on the main branch."
  2. The workflow presents this token to Azure. It's essentially saying "GitHub vouches for me."
  3. Azure checks its trust configuration. You've pre-configured Azure to trust tokens from GitHub that match specific criteria (repo, branch, environment, etc.).
  4. If the trust checks out, Azure issues credentials. These are also short-lived—typically valid for an hour or less.
  5. Your workflow uses those credentials to do its thing. Deploy infrastructure, push to a container registry, whatever you need.

 

The key insight: there's never a long-lived secret sitting in your GitHub repo. Every authentication happens in real-time with ephemeral tokens.

The Terraform

I've put together a minimal Terraform configuration that creates everything you need on the Azure side. You can grab the full code from our GitHub repo.

The main configuration creates an App Registration, Service Principal, and the federated credential that establishes trust with GitHub:

Article content

That ‘subject’ claim is the key—it tells Azure to only trust tokens from your specific repo and branch. A workflow running from a feature branch? Denied. A workflow from a different repo? Denied. This is Zero Trust in action.

The GitHub Actions Workflow

On the GitHub side, here's what your workflow looks like:

Article content

Notice what's not there? No client-secret. The azure/login action handles the OIDC token exchange automatically.

"Wait, you said no secrets!" I hear you. The values stored in GitHub secrets here aren't authentication secrets—they're just identifiers. They're like your mailing address—knowing them doesn't let someone into your house. The authentication happens through the OIDC token exchange, not through these values.

Article content

The Security Win

Let's recap why we should do this:

  • No long-lived secrets - Nothing to leak, nothing to rotate
  • Scoped trust - Azure only accepts tokens from your specific repo and branch
  • Short-lived credentials - Even if somehow intercepted, they expire quickly
  • Audit trail - Every authentication is logged with the exact workflow that requested it

 

And it’s easy! I hope if you’re not already doing this, that you start to leverage OIDC to stop yourself from provisioning more static secrets.