top of page
Search

All about GitHub Actions and Azure OpenID Connect

Writer: Nathan
Nathan
12 hours ago
6 min read

How do you securely login to Azure from a GitHub Actions workflow?  Well, you could just use the Azure/login action and configure it to use an Entra App Registration and Client Secret.  But who wants to deal with managing, storing, and rotating Client Secrets?  Not me.  That’s where OpenID Connect (OIDC) comes into play.  This allows you to securely connect your GitHub Actions workflow to your Azure environment without using a password.  This supports both Entra App Registrations and Azure User-Assigned Managed Identities.


All you need to do is configure a Federated Credential on your App Registration or Managed Identity.  This Federated Credential tells the identity to trust tokens issued by your GitHub repository.  The Federated Credential can be configured so that it trusts:


  1. Any workflow job that references a specific GitHub Environment on a specific Repo

  2. Any workflow triggered by a Pull Request event on a specific Repo

  3. Any workflow triggered on a specific Branch on a specific Repo

  4. Any workflow triggered on a specific Tag on a specific Repo


Notice I used the word "specific" a lot in the 4 examples above.  That’s because you have to be explicit about which exact repo the Federated Credential should trust.  On top of that, there is a hard limit of 20 Federated Credentials that you can create per App Registration or Managed Identity.  That means if you want to use a single identity that has Federated Credentials to more than 20 different repos, then you will have to use some newer tricks.  You see, Azure and GitHub have both been making some much-needed changes and updates in this space, and we will go over all of these changes further below.



Federated Credentials using a Subject


There are currently 2 ways to create a Federated Credential on your identity.  Let's start by discussing the first way, where the Federated Credential includes a "Subject" identifier.  When using this method, you need to know 3 key important pieces of information:


One, the "Issuer" (abbreviated as "iss").


  • This should point to GitHub's OIDC provider:  "https://token.actions.githubusercontent.com"

  • In most cases, you won’t need to use anything other than this value.  However, if you have a GitHub Enterprise then you can perform some extra security hardening by modifying this value.  Details about this optional extra hardening for GitHub Enterprises will not be discussed in this article.


Two, the "Audience" (abbreviated as "aud")


  • This field is mandatory and for Entra ID it should be set to "api://AzureADTokenExchange"


Three, the "Subject" (abbreviated as "sub")


  • By default, this points to one of the 4 options mentioned previously in this article: a GitHub Environment, a Pull Request, a Branch, or a Tag.

  • There are options to customize this, and we will discuss those options below.



Deep-dive on Subject


For repositories created prior to July 15, 2026 you are allowed to use an older-style Subject value which uses "mutable" names.  This allows you to simply use an Owner name and a Repo name in your Subject.  Be warned, this is a pretty big security risk, because "if a repository or organization name was recycled, then a new owner could mint tokens with the same subject claim, potentially gaining unauthorized access to cloud resources that still trusted the original identity."


Here are some example Subject values that use the "mutable" format:


  • Environment:

    • repo:<OwnerName>/<RepoName>:environment:<EnvName>

  • Branch:

    • repo:<OwnerName>/<RepoName>:ref:refs/heads/<BranchName>

  • Pull Request:

    • repo:<OwnerName>/<RepoName>:pull_request

  • Tag:

    • repo:<OwnerName>/<RepoName>:ref:refs/tags/<TagName>


GitHub has now updated the default behavior, and repositories created after July 15, 2026 are now forced to use the new "immutable" names in their Subject value.  This adds an immutable Owner ID value and Repo ID value to your Subject. Doing this solves the security problem mentioned above.


Here are some example Subject values that use the "immutable" format:


  • Environment:

    • repo:<OwnerName>@<ID>/<RepoName>@<ID>:environment:<EnvName>

  • Branch:

    • repo:<OwnerName>@<ID>/<RepoName>@<ID>:ref:refs/heads/<BranchName>

  • Pull Request:

    • repo:<OwnerName>@<ID>/<RepoName>@<ID>:pull_request

  • Tag:

    • repo:<OwnerName>@<ID>/<RepoName>@<ID>:ref:refs/tags/<TagName>


A couple of notes:


  • If you rename or transfer an older repo it will automatically get updated to the new "immutable" Subject format.

  • Older repos can opt-in to using the new "immutable" Subject format by toggling a switch found at Settings > Actions > OIDC > Use immutable subject claim.

  • If you have a GitHub Organization, then this setting can also be toggled globally at the Organization level.  By default, old repos belonging to the Organization will use the Org setting.  However, you can override this on a per-repo basis, if desired.



Customizable subject claims


If you don’t want to use the default Subject value provided by GitHub, then you can provide your own custom one.  On your repo go to Settings > Actions > OIDC > un-check "Use default template" > then modify "Subject claim template" as necessary.


  • See the supported options here:  https://docs.github.com/en/actions/reference/security/oidc

  • On your identity's Federated Credential, you would specify the custom Subject like this:  key:value

  • If you specified multiple fields in your custom Subject claim template, then the Federated Credential would use a Subject like this:  key1:value1:key2:value2


If you have a GitHub Organization, you can also configure a custom Subject claim at the Org level.  Any repos in the Org will use this custom Subject claim by default, but you can override this default on a per-repo basis.


"job_workflow_ref" is a popular custom claim and has this format:


  • When the workflow is triggered on a branch:

    • job_workflow_ref:<OwnerName>/<RepoName>/.github/workflows/<FileName>.yaml@refs/heads/<BranchName>

  • When the workflow is triggered on a tag:

    • job_workflow_ref:<OwnerName>/<RepoName>/.github/workflows/<FileName>.yaml@refs/tags/<TagName>


A couple of notes on job_workflow_ref:


  • If you are using GitHub Reusable Workflows, then job_workflow_ref points at the Reusable Workflow file, and not the "calling" Workflow’s file.

    • This is a fantastic way to lock down OIDC.  For example, doing this would allow you to require a specific Reusable Workflow from a specific Repo using a specific Branch.  This means the identity would only be able to login to Azure when these specific conditions are met.

  • The value is NOT affected by the "immutable" Subject claim setting, it always shows just Owner name and Repo name with no IDs after them



Federated Credentials using a Claims Matching Expression


Remember when I said there are currently 2 ways to create a Federated Credential on your identity?  We’ve already talked about the first way in detail (Subject).  The 2nd way is called Flexible Federated Credentials, and these use a Claims Matching Expression instead of a Subject. This allows you to use wildcard matching in your expression.  If you are hitting the previously mentioned limit of 20 Federated Credentials per identity, then this is a great way to get around that limitation.


One big caveat is that Flexible Federated Credentials are still in Preview.  Both Entra App Registrations and Azure User-Assigned Managed Identities are supported.  But, at the time of this writing, the Azure Portal only lets you add a Claims Matching Expression to an Entra App Registration.  The option to add one to a User-Assigned Managed Identity is currently missing from the portal.  You will have to resort to other means for Managed Identities, such as the Azure CLI, ARM Templates, or Bicep templates.



Let’s dive into how the expressions and wildcard matching works:


  • The "matches" keyword let you do wildcard matching

    • Single characters can be matched by the ? character, and you can use multiple ? characters together

    • Any number of characters can be matched by the * character

  • The "eq" keyword does exact matching

  • The "and" keyword lets you combine expressions


At a minimum, your Federated Credential’s Claims Matching Expression must match the Subject (sub) claim plus one or both of the following immutable claims: repository_id or repository_owner_id.  Optionally, you can add extra claims to your expression, if desired.


Some popular supported claims for GitHub:


  • "sub" supports both "eq" and "matches"

  • "repository_id" supports only "eq"

  • "repository_owner_id" supports only "eq"

  • "job_workflow_ref" supports both "eq" and "matches"


Example Claims Matching Expression:


  • claims['sub'] matches 'repo:*/*:ref:refs/heads/*' and claims[‘repository_owner_id’] eq ‘123456’

  • This particular example would match any Branch from any Repo which is owned by the Owner who has the ID of 123456.


Important:  If your Repo is configured for "immutable" claims then your claims matching expressions will need to match the Owner ID and Repo ID values in the Subject.



GitHub Custom Properties included as extra claims in the OIDC token


If you have a GitHub Organization, then you can define Custom Properties for your Repos to use.  GitHub now lets you add these Custom Properties as extra claims to your OIDC token.  You can go to your Organization Settings > Actions > OIDC > Select custom properties for inclusion.


Each Custom Property added this way appears as a separate claim in the OIDC token, where the claim’s name follows this pattern:  repo_property_<name>


Every repository in the Org which has a value for that given Custom Property will automatically include it as an extra claim in their OIDC token.  You can then look for this value in your customizable Subject claim, or your Claims Matching Expression.




Putting it all together


  1. Create an Entra App Registration or an Azure User-Assigned Managed Identity

  2. Assign some Azure RBAC roles to your new identity (whatever resources it will need permissions to)

  3. Create a new Federated Credential on the new identity which ties it to GitHub

  4. In your GitHub Workflow use the Azure/login action to log into Azure using the new identity using password-less OIDC authentication. Here is a link to the steps for this particular scenario.

  5. Your GitHub Workflow can now run commands to interact with Azure resources.



References



Comments


bottom of page