<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Workload Identity on BuiltWithCaffeine</title><link>https://blog.builtwithcaffeine.cloud/tags/workload-identity/</link><description>Recent content in Workload Identity on BuiltWithCaffeine</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>- BuiltWithCaffeine</copyright><lastBuildDate>Mon, 21 Sep 2026 10:00:00 +0000</lastBuildDate><atom:link href="https://blog.builtwithcaffeine.cloud/tags/workload-identity/rss.xml" rel="self" type="application/rss+xml"/><item><title>GitHub Actions OIDC: Moving to Immutable Subject Claims</title><link>https://blog.builtwithcaffeine.cloud/posts/github-actions-immutable-subject-claims/</link><pubDate>Mon, 21 Sep 2026 10:00:00 +0000</pubDate><guid>https://blog.builtwithcaffeine.cloud/posts/github-actions-immutable-subject-claims/</guid><description>&lt;p&gt;GitHub Actions can authenticate to Azure without storing a long-lived client secret in the repository. With OpenID Connect (OIDC), a workflow requests a short-lived token from GitHub and Microsoft Entra ID exchanges that token for an Azure access token.&lt;/p&gt;
&lt;p&gt;That is a much better authentication model than keeping a service principal secret in GitHub. However, the trust relationship still needs to be designed carefully. Microsoft Entra federated identity credentials match claims in the GitHub token, especially the &lt;code&gt;sub&lt;/code&gt; (subject) claim. If that subject is built only from repository and owner names, it can change or be recycled.&lt;/p&gt;
&lt;p&gt;GitHub now supports an immutable subject format that includes the stable owner and repository IDs. This post explains why that matters and how to migrate an Azure federated identity credential without taking an existing workflow offline.&lt;/p&gt;
&lt;h2 id="the-problem-with-name-based-subjects"&gt;The problem with name-based subjects
&lt;/h2&gt;&lt;p&gt;Historically, a GitHub Actions workflow running on the &lt;code&gt;main&lt;/code&gt; branch of &lt;code&gt;contoso/payments-api&lt;/code&gt; produced a subject similar to this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;repo:contoso/payments-api:ref:refs/heads/main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The value is readable, but both names are mutable. An organisation or repository can be renamed, transferred, deleted, and potentially recreated. If a federated identity credential remains configured for the old value, a different repository could eventually produce the same subject.&lt;/p&gt;
&lt;p&gt;This is known as &lt;code&gt;subject recycling&lt;/code&gt;. The credential is still present, but the workload it originally trusted is no longer the workload producing the matching token. A stale credential that trusts a workload that no longer exists is also called a &lt;code&gt;dangling federated identity credential&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The risk is not unique to GitHub. The general rule for workload federation is simple: when an issuer provides a stable identifier, anchor trust to that identifier rather than to a display name or path.&lt;/p&gt;
&lt;h2 id="what-is-an-immutable-github-subject"&gt;What is an immutable GitHub subject?
&lt;/h2&gt;&lt;p&gt;The immutable format keeps the names for readability and adds the permanent owner and repository IDs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;repo:&amp;lt;owner&amp;gt;@&amp;lt;owner_id&amp;gt;/&amp;lt;repo&amp;gt;@&amp;lt;repo_id&amp;gt;:ref:refs/heads/main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;repo:contoso@5544123/payments-api@821093847:ref:refs/heads/main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The owner and repository IDs are assigned by GitHub and are not reused. Renaming or transferring the repository therefore does not make the original subject belong to a different repository.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; separator is intentional: GitHub usernames and repository names cannot contain it.&lt;/p&gt;
&lt;p&gt;For example, the example repository used with this post is &lt;a class="link" href="https://github.com/builtwithcaffeine/bwc-github-federation-example" target="_blank" rel="noopener"
&gt;&lt;code&gt;builtwithcaffeine/bwc-github-federation-example&lt;/code&gt;&lt;/a&gt;. The repository includes the branch- and environment-based GitHub Actions workflows discussed in this post, so you can inspect the configuration and try the pattern yourself. Its immutable branch subject is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;repo:builtwithcaffeine@141853123/bwc-github-federation-example@141853123:ref:refs/heads/main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The IDs in these examples are illustrative. Always retrieve the current values from GitHub before creating a credential. For example, the GitHub CLI can return the owner and repository IDs together:&lt;/p&gt;
&lt;p&gt;&lt;style&gt;
.code-switcher {
position: relative;
margin: 1.75rem 0;
border: 1px solid var(--card-separator-color);
border-radius: var(--card-border-radius);
background: var(--card-background);
padding: clamp(1rem, 2vw, 1.35rem);
box-shadow: var(--shadow-l1);
transition: box-shadow 0.2s ease, border-color 0.2s ease;
}
.code-switcher:hover {
box-shadow: var(--shadow-l2);
}
[data-scheme="dark"] .code-switcher {
box-shadow: none;
border-color: rgba(255, 255, 255, 0.12);
}
.code-switcher__input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.code-switcher__controls-wrapper {
display: flex;
justify-content: flex-start;
flex: 1 1 auto;
min-width: 0;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.code-switcher__controls-wrapper::-webkit-scrollbar {
display: none;
}
.code-switcher__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.75rem;
margin-bottom: 1rem;
}
.code-switcher__controls {
--switch-count: 2;
--switch-control-font-size: 0.95rem;
--switch-control-line-height: 1.25;
--switch-control-padding-y: 0.5rem;
--switch-control-padding-x: 0.85rem;
--switch-control-radius: calc(var(--card-border-radius) - 6px);
position: relative;
display: inline-flex;
gap: 0.35rem;
padding: 0.35rem;
border-radius: calc(var(--card-border-radius) - 4px);
background: rgba(0, 0, 0, 0.04);
border: 1px solid var(--card-separator-color);
margin-bottom: 0;
flex-wrap: nowrap;
justify-content: flex-start;
white-space: nowrap;
min-width: max-content;
max-width: 100%;
}
[data-scheme="dark"] .code-switcher__controls {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.14);
}
.code-switcher__copy {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
padding: 0.4rem 0.8rem;
border-radius: var(--tag-border-radius);
border: 1px solid var(--card-separator-color);
background: var(--card-background);
color: var(--card-text-color-main);
font-weight: 600;
font-size: 0.875rem;
line-height: var(--switch-control-line-height);
cursor: pointer;
min-width: 7.25ch;
margin-left: auto;
align-self: flex-start;
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.code-switcher__copy:focus-visible,
.code-switcher__label:focus-visible {
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
.code-switcher__copy:hover {
background: var(--card-background-highlight, rgba(0, 0, 0, 0.05));
box-shadow: var(--shadow-l1);
}
[data-scheme="dark"] .code-switcher__copy {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.14);
color: rgba(255, 255, 255, 0.85);
}
[data-scheme="dark"] .code-switcher__copy:hover {
background: rgba(255, 255, 255, 0.16);
color: #fff;
}
.code-switcher__copy:disabled,
.code-switcher__copy[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.6;
box-shadow: none;
}
.code-switcher__copy.is-success {
background: var(--accent-color);
border-color: var(--accent-color);
color: var(--accent-color-text);
box-shadow: var(--shadow-l2);
}
[data-scheme="dark"] .code-switcher__copy.is-success {
background: var(--accent-color-darker);
border-color: var(--accent-color-darker);
color: #111;
}
.code-switcher__copy.is-error {
background: #c62828;
border-color: #c62828;
color: #fff;
box-shadow: var(--shadow-l2);
}
[data-scheme="dark"] .code-switcher__copy.is-error {
background: #ef5350;
border-color: #ef5350;
color: #111;
}
.code-switcher__slider {
display: none;
}
.code-switcher__label {
display: inline-flex;
flex: 1 1 auto;
justify-content: center;
align-items: center;
min-width: 140px;
padding: var(--switch-control-padding-y) var(--switch-control-padding-x);
border-radius: var(--switch-control-radius);
text-align: center;
font-weight: 600;
color: var(--card-text-color-secondary);
cursor: pointer;
-webkit-user-select: none;
user-select: none;
line-height: var(--switch-control-line-height);
font-size: var(--switch-control-font-size);
transition: color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
}
[data-scheme="dark"] .code-switcher__label {
color: rgba(255, 255, 255, 0.65);
}
.code-switcher__label:hover {
color: var(--card-text-color-main);
}
[data-scheme="dark"] .code-switcher__label:hover {
color: rgba(255, 255, 255, 0.85);
}
.code-switcher__label .code-switcher__label-title {
display: block;
font-size: 1em;
}
.code-switcher__label .code-switcher__label-subtitle {
display: block;
font-size: 0.72rem;
letter-spacing: 0.04em;
font-weight: 500;
opacity: 0.7;
text-transform: uppercase;
}
.code-switcher__label.is-active {
background: var(--accent-color);
color: var(--accent-color-text);
box-shadow: var(--shadow-l2);
}
[data-scheme="dark"] .code-switcher__label.is-active {
background: var(--accent-color-darker);
color: #111;
box-shadow: none;
}
.code-switcher__label.has-focus {
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
.code-switcher__panels {
border-radius: calc(var(--card-border-radius) - 4px);
overflow: clip;
}
.code-switcher__panel {
display: none;
border-radius: inherit;
}
.code-switcher__panel.is-active {
display: block;
}
.code-switcher__panel .highlight {
margin: 0;
border-radius: inherit;
position: relative;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
-ms-overflow-style: none;
}
.code-switcher__panel .highlight::-webkit-scrollbar {
display: none;
}
.code-switcher--show-scrollbar .code-switcher__panel .highlight {
overflow-x: scroll;
scrollbar-width: thin;
scrollbar-color: var(--accent-color) var(--card-separator-color);
}
.code-switcher--show-scrollbar .code-switcher__panel .highlight::-webkit-scrollbar {
display: block;
height: 0.55rem;
}
.code-switcher--show-scrollbar .code-switcher__panel .highlight &gt; .chroma {
width: max-content;
min-width: 100%;
}
.code-switcher--show-scrollbar .code-switcher__panel .highlight::-webkit-scrollbar-track {
background: var(--card-separator-color);
}
.code-switcher--show-scrollbar .code-switcher__panel .highlight::-webkit-scrollbar-thumb {
background: var(--accent-color);
border-radius: 999px;
}
.code-switcher__panel .highlight .chroma .lntable &gt; tbody &gt; tr &gt; td:last-child {
scrollbar-width: none;
-ms-overflow-style: none;
}
.code-switcher__panel .highlight .chroma .lntable &gt; tbody &gt; tr &gt; td:last-child::-webkit-scrollbar {
display: none;
}
.code-switcher--show-scrollbar .code-switcher__panel .highlight .chroma .lntable &gt; tbody &gt; tr &gt; td:last-child {
scrollbar-width: thin;
}
.code-switcher--show-scrollbar .code-switcher__panel .highlight .chroma .lntable &gt; tbody &gt; tr &gt; td:last-child::-webkit-scrollbar {
display: block;
height: 0.55rem;
}
.code-switcher__panel pre {
margin: 0;
padding-block: 0.75rem;
font-family: var(--code-font-family);
background: var(--pre-background-color);
color: var(--pre-text-color);
position: relative;
overflow: visible;
scrollbar-width: none;
-ms-overflow-style: none;
}
.code-switcher__sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.code-switcher__panel pre::-webkit-scrollbar {
display: none;
}
.code-switcher__panel .copyCodeButton {
display: none;
}
.code-switcher:not(.is-ready) .code-switcher__panel {
display: block;
}
@media (max-width: 680px) {
.code-switcher__header {
flex-direction: column;
align-items: stretch;
gap: 0.65rem;
}
.code-switcher__controls {
width: 100%;
justify-content: flex-start;
}
.code-switcher__copy {
align-self: flex-end;
margin-left: 0;
}
.code-switcher__label {
flex: 0 0 auto;
min-width: 120px;
}
}
&lt;/style&gt;
&lt;script&gt;
(function () {
if (window.__codeSwitcherLoaded) {
return;
}
window.__codeSwitcherLoaded = true;
var init = function () {
var switchers = document.querySelectorAll('[data-code-switcher]');
if (!switchers.length) {
return;
}
switchers.forEach(function (wrapper) {
var inputs = wrapper.querySelectorAll('.code-switcher__input');
if (!inputs.length) {
return;
}
var labels = wrapper.querySelectorAll('.code-switcher__label');
var panels = wrapper.querySelectorAll('.code-switcher__panel');
var copyButton = wrapper.querySelector('[data-code-switch-copy]');
var statusNode = wrapper.querySelector('[data-code-switch-status]');
var copyLabel = copyButton ? (copyButton.getAttribute('data-copy-label') || copyButton.textContent || 'Copy') : 'Copy';
var copiedLabel = copyButton ? (copyButton.getAttribute('data-copied-label') || 'Copied!') : 'Copied!';
var errorLabel = copyButton ? (copyButton.getAttribute('data-error-label') || 'Error') : 'Error';
var count = inputs.length;
wrapper.style.setProperty('--switch-count', count);
wrapper.classList.add('is-ready');
if (copyButton) {
copyButton.textContent = copyLabel;
}
var activeIndex = 0;
var feedbackTimeout = null;
var setStatus = function (message) {
if (statusNode) {
statusNode.textContent = message || '';
}
};
var setCopyFeedback = function (state) {
if (!copyButton) {
return;
}
if (feedbackTimeout) {
clearTimeout(feedbackTimeout);
feedbackTimeout = null;
}
copyButton.classList.remove('is-success');
copyButton.classList.remove('is-error');
if (state === 'success') {
copyButton.textContent = copiedLabel;
copyButton.classList.add('is-success');
setStatus('Code copied to clipboard');
} else if (state === 'error') {
copyButton.textContent = errorLabel;
copyButton.classList.add('is-error');
setStatus('Unable to copy code to clipboard');
} else {
copyButton.textContent = copyLabel;
setStatus('');
}
if (state === 'success' || state === 'error') {
feedbackTimeout = setTimeout(function () {
copyButton.textContent = copyLabel;
copyButton.classList.remove('is-success');
copyButton.classList.remove('is-error');
setStatus('');
}, 1500);
}
};
var resolveCodeElement = function (panel) {
if (!panel) {
return null;
}
var lineTableCode = panel.querySelector('.chroma .lntable .lntd:last-child code');
if (lineTableCode) {
return lineTableCode;
}
var preferred = panel.querySelectorAll('code[data-lang]');
if (preferred &amp;&amp; preferred.length) {
return preferred[preferred.length - 1];
}
var codeNodes = panel.querySelectorAll('code');
if (codeNodes &amp;&amp; codeNodes.length) {
return codeNodes[codeNodes.length - 1];
}
var preNode = panel.querySelector('pre');
return preNode || null;
};
var activate = function (index) {
wrapper.style.setProperty('--active-index', index);
inputs.forEach(function (input, idx) {
var isActive = idx === index;
input.checked = isActive;
if (labels[idx]) {
labels[idx].classList.toggle('is-active', isActive);
labels[idx].setAttribute('aria-selected', isActive ? 'true' : 'false');
labels[idx].setAttribute('tabindex', isActive ? '0' : '-1');
}
if (panels[idx]) {
panels[idx].classList.toggle('is-active', isActive);
panels[idx].hidden = !isActive;
panels[idx].setAttribute('aria-hidden', isActive ? 'false' : 'true');
}
});
activeIndex = index;
if (copyButton) {
var activePanel = panels[index];
var codeElement = resolveCodeElement(activePanel);
var clipboardAvailable = typeof navigator !== 'undefined' &amp;&amp; navigator.clipboard &amp;&amp; typeof navigator.clipboard.writeText === 'function';
var isDisabled = !codeElement || !clipboardAvailable;
copyButton.disabled = isDisabled;
copyButton.setAttribute('aria-disabled', isDisabled ? 'true' : 'false');
setCopyFeedback('default');
}
};
var current = Array.prototype.findIndex.call(inputs, function (input) {
return input.checked;
});
if (current &lt; 0) {
current = 0;
}
activate(current);
inputs.forEach(function (input, idx) {
input.addEventListener('change', function () {
if (input.checked) {
activate(idx);
}
});
input.addEventListener('focus', function () {
if (labels[idx]) {
labels[idx].classList.add('has-focus');
}
});
input.addEventListener('blur', function () {
if (labels[idx]) {
labels[idx].classList.remove('has-focus');
}
});
});
labels.forEach(function (label, idx) {
label.addEventListener('click', function () {
if (!inputs[idx].checked) {
inputs[idx].checked = true;
inputs[idx].dispatchEvent(new Event('change', { bubbles: true }));
}
});
label.addEventListener('keydown', function (evt) {
if (evt.key === 'ArrowRight' || evt.key === 'ArrowDown') {
evt.preventDefault();
var next = (idx + 1) % count;
inputs[next].focus();
inputs[next].checked = true;
inputs[next].dispatchEvent(new Event('change', { bubbles: true }));
} else if (evt.key === 'ArrowLeft' || evt.key === 'ArrowUp') {
evt.preventDefault();
var prev = (idx - 1 + count) % count;
inputs[prev].focus();
inputs[prev].checked = true;
inputs[prev].dispatchEvent(new Event('change', { bubbles: true }));
} else if (evt.key === 'Home') {
evt.preventDefault();
inputs[0].focus();
inputs[0].checked = true;
inputs[0].dispatchEvent(new Event('change', { bubbles: true }));
} else if (evt.key === 'End') {
evt.preventDefault();
inputs[count - 1].focus();
inputs[count - 1].checked = true;
inputs[count - 1].dispatchEvent(new Event('change', { bubbles: true }));
} else if (evt.key === 'Enter' || evt.key === ' ') {
evt.preventDefault();
if (!inputs[idx].checked) {
inputs[idx].checked = true;
inputs[idx].dispatchEvent(new Event('change', { bubbles: true }));
}
}
});
});
var clipboardSupported = typeof navigator !== 'undefined' &amp;&amp; navigator.clipboard &amp;&amp; typeof navigator.clipboard.writeText === 'function';
if (copyButton &amp;&amp; clipboardSupported) {
copyButton.addEventListener('click', function () {
if (copyButton.disabled) {
return;
}
var activePanel = panels[activeIndex];
if (!activePanel) {
return;
}
var codeElement = resolveCodeElement(activePanel);
if (!codeElement) {
return;
}
var codeText = codeElement.innerText || codeElement.textContent || '';
if (!codeText) {
return;
}
navigator.clipboard.writeText(codeText).then(function () {
setCopyFeedback('success');
}).catch(function () {
setCopyFeedback('error');
});
});
} else if (copyButton) {
copyButton.disabled = true;
copyButton.setAttribute('aria-disabled', 'true');
copyButton.title = 'Clipboard access is not supported in this browser';
}
});
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init, { once: true });
} else {
init();
}
})();
&lt;/script&gt;
&lt;div class="code-switcher code-switcher--show-scrollbar" id="github-federation-get-ids" data-code-switcher="2" data-code-switch-copy="true"&gt;&lt;input type="radio" name="github-federation-get-ids-selector" id="github-federation-get-ids-option-0" class="code-switcher__input" checked&gt;&lt;input type="radio" name="github-federation-get-ids-selector" id="github-federation-get-ids-option-1" class="code-switcher__input"&gt;&lt;div class="code-switcher__header"&gt;
&lt;div class="code-switcher__controls-wrapper"&gt;
&lt;div class="code-switcher__controls" role="tablist" aria-label="Code example language tabs" aria-orientation="horizontal"&gt;&lt;label class="code-switcher__label" id="github-federation-get-ids-option-0-label" for="github-federation-get-ids-option-0" role="tab" aria-controls="github-federation-get-ids-panel-0" aria-selected="true" tabindex="0"&gt;
&lt;span class="code-switcher__label-title"&gt;PowerShell&lt;/span&gt;&lt;/label&gt;&lt;label class="code-switcher__label" id="github-federation-get-ids-option-1-label" for="github-federation-get-ids-option-1" role="tab" aria-controls="github-federation-get-ids-panel-1" aria-selected="false" tabindex="-1"&gt;
&lt;span class="code-switcher__label-title"&gt;Bash&lt;/span&gt;&lt;/label&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;button type="button"
class="code-switcher__copy"
data-code-switch-copy
data-copy-label="Copy"
data-copied-label="Copied!"
data-error-label="Error"
aria-label="Copy current code example"&gt;
Copy
&lt;/button&gt;
&lt;span class="code-switcher__sr-only" data-code-switch-status aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-switcher__panels"&gt;&lt;div class="code-switcher__panel" id="github-federation-get-ids-panel-0" role="tabpanel" aria-labelledby="github-federation-get-ids-option-0-label"&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$owner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;builtwithcaffeine&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bwc-github-federation-example&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;gh&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$owner&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$repository&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;-jq&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{owner_name: .owner.login, owner_id: .owner.id, repo_name: .name, repo_id: .id}&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class="code-switcher__panel" id="github-federation-get-ids-panel-1" role="tabpanel" aria-labelledby="github-federation-get-ids-option-1-label"&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;builtwithcaffeine&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bwc-github-federation-example&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gh api &lt;span class="s2"&gt;&amp;#34;repos/&lt;/span&gt;&lt;span class="nv"&gt;$owner&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$repository&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --jq &lt;span class="s1"&gt;&amp;#39;{owner_name: .owner.login, owner_id: .owner.id, repo_name: .name, repo_id: .id}&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;style type="text/css"&gt;
.box-shortcode {
display: flex;
gap: 0.75em;
padding: 1.6em;
padding-top: 1.4em;
line-height: 1.6;
margin: 1em 0 2em;
border-radius: 8px;
color: #000;
background: #f3ebe850;
align-items: flex-start;
}
[data-scheme="dark"] .box-shortcode {
color: #fff;
background: #333;
}
.box-shortcode.warning { background: #ff6b6b4f; }
.box-shortcode.info { background: #0089e41c; box-shadow: 3px 3px 5px #0089e410; }
.box-shortcode.important { background: #f7ec2c7d; }
.box-shortcode.tip { background: #a3ffa34d; box-shadow: 3px 3px 5px #0089e410; }
.icon-box {
display: inline-flex;
flex: 0 0 auto;
line-height: 1;
align-self: flex-start;
margin-top: 0.2em;
}
.icon-box svg {
height: 1.2em;
width: 1.2em;
fill: currentColor;
}
.box-shortcode .box-body {
flex: 1 1 auto;
min-width: 0;
}
.box-shortcode .box-body &gt; :first-child {
margin-top: 0;
}
.box-shortcode .box-body &gt; :last-child {
margin-bottom: 0;
}
.box-shortcode .sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.box-shortcode :not(pre) &gt; code {
border-radius: 4px;
padding: 0.15em 0.45em;
background: #f1f3f5;
border: 1px solid #e0e4e8;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
font-size: 0.85em;
line-height: 1.25em;
white-space: normal;
display: inline-block;
vertical-align: baseline;
}
.box-shortcode p &gt; code,
.box-shortcode li &gt; code,
.box-shortcode span &gt; code,
.box-shortcode div &gt; code,
.box-shortcode em &gt; code,
.box-shortcode strong &gt; code {
background: #f1f3f5;
border: 1px solid #e0e4e8;
padding: 0.15em 0.45em;
border-radius: 4px;
font-weight: 600;
}
[data-scheme="dark"] .box-shortcode :not(pre) &gt; code {
background: #2d333b;
border-color: #3b424b;
color: #e6edf3;
}
[data-scheme="dark"] .box-shortcode p &gt; code,
[data-scheme="dark"] .box-shortcode li &gt; code,
[data-scheme="dark"] .box-shortcode span &gt; code,
[data-scheme="dark"] .box-shortcode div &gt; code,
[data-scheme="dark"] .box-shortcode em &gt; code,
[data-scheme="dark"] .box-shortcode strong &gt; code {
background: #2d333b;
border-color: #3b424b;
color: #e6edf3;
font-weight: 600;
}
.box-shortcode strong,
.box-shortcode strong code,
.box-shortcode p strong code,
.box-shortcode li strong code {
font-weight: 600 !important;
}
.box-shortcode pre,
.box-shortcode .highlight {
margin: 1em auto;
overflow: hidden;
display: block;
border-radius: 6px;
box-sizing: border-box;
position: relative;
isolation: isolate;
max-width: 100%;
}
.box-shortcode .highlight {
padding-left: 0;
padding-right: 0;
margin-right: 0;
width: 100%;
max-width: 100%;
}
@media (min-width: 1100px) {
.box-shortcode pre,
.box-shortcode .highlight {
max-width: 900px;
}
}
.box-shortcode pre code,
.box-shortcode .highlight pre,
.box-shortcode .highlight code {
display: block;
border-radius: inherit;
background-clip: padding-box;
margin: 0;
}
.box-shortcode .highlight-wrapper {
margin: 1em auto;
border-radius: 6px;
overflow: hidden;
box-sizing: border-box;
isolation: isolate;
max-width: 100%;
}
@media (min-width: 1100px) {
.box-shortcode .highlight-wrapper {
max-width: 900px;
}
}
.box-shortcode .highlight-wrapper .highlight {
border-radius: inherit;
overflow: hidden;
}
.box-shortcode .highlight button.copy {
position: absolute;
top: 0.5em;
right: 0.5em;
z-index: 1;
border-radius: 4px;
}
&lt;/style&gt;
&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg"&gt;
&lt;symbol id="tip-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet"&gt;
&lt;path
d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/&gt;
&lt;/symbol&gt;
&lt;symbol id="important-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet"&gt;
&lt;path
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/&gt;
&lt;/symbol&gt;
&lt;symbol id="warning-box" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet"&gt;
&lt;path
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/&gt;
&lt;/symbol&gt;
&lt;symbol id="info-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet"&gt;
&lt;path
d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/&gt;
&lt;/symbol&gt;
&lt;/svg&gt;&lt;div class="box box-shortcode important" data-variant="important" role="note"&gt;
&lt;span class="icon-box"&gt;
&lt;span class="sr-only"&gt;Important&lt;/span&gt;
&lt;svg&gt;&lt;use href="#important-box"&gt;&lt;/use&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="box-body"&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Immutable subjects apply to repositories on GitHub.com. They are not available on GitHub Enterprise Server. Existing repositories also keep the old format until they opt in, while repositories created after July 15, 2026 use the immutable format by default.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The branch, tag, environment, or pull request context still appears after the repository segment. Only the identity of the owner and repository becomes immutable.&lt;/p&gt;
&lt;h2 id="how-the-azure-trust-relationship-works"&gt;How the Azure trust relationship works
&lt;/h2&gt;&lt;p&gt;An Azure federated identity credential normally checks three important values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Issuer:&lt;/strong&gt; &lt;code&gt;https://token.actions.githubusercontent.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subject:&lt;/strong&gt; the GitHub OIDC &lt;code&gt;sub&lt;/code&gt; claim&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audience:&lt;/strong&gt; &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The subject should also be scoped as narrowly as the deployment requires. For example, a production deployment from the &lt;code&gt;main&lt;/code&gt; branch might trust this exact subject:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;repo:contoso@5544123/payments-api@821093847:ref:refs/heads/main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If the workflow deploys through a GitHub Environment instead, the subject includes the environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;repo:contoso@5544123/payments-api@821093847:environment:production
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not use a broad subject just because it is easier to configure. A credential matching every branch or every repository gives a compromised workflow more opportunity to obtain an Azure token.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites
&lt;/h2&gt;&lt;p&gt;Before starting the migration, check the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The repository is hosted on GitHub.com.&lt;/li&gt;
&lt;li&gt;The workflow already uses OIDC or is ready to use it.&lt;/li&gt;
&lt;li&gt;The Microsoft Entra application or user-assigned managed identity has a federated identity credential.&lt;/li&gt;
&lt;li&gt;You have permission to update the GitHub OIDC settings and the Entra application.&lt;/li&gt;
&lt;li&gt;You know the GitHub owner ID and repository ID.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The owner and repository IDs are available through GitHub&amp;rsquo;s OIDC settings and REST API. GitHub also provides a preview mechanism so that you can confirm the subject a workflow will emit before changing the Entra trust policy.&lt;/p&gt;
&lt;h2 id="the-migration-strategy"&gt;The migration strategy
&lt;/h2&gt;&lt;p&gt;The safest migration is additive rather than destructive:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the immutable owner and repository IDs.&lt;/li&gt;
&lt;li&gt;Create a new Entra federated identity credential for the immutable subject.&lt;/li&gt;
&lt;li&gt;Enable immutable subjects for the repository or organisation in GitHub.&lt;/li&gt;
&lt;li&gt;Run the workflow and confirm that Azure authentication succeeds.&lt;/li&gt;
&lt;li&gt;Remove the old name-based credential.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Creating the new credential before changing GitHub keeps the existing workflow working during the transition. Removing the old credential only after a successful run prevents the migration from becoming an unnecessary outage.&lt;/p&gt;
&lt;h2 id="create-the-immutable-federated-credential"&gt;Create the immutable federated credential
&lt;/h2&gt;&lt;p&gt;Save a credential definition such as the following as &lt;code&gt;credential.json&lt;/code&gt;. Replace the example values with the IDs and subject for your own repository.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;payments-api-main-immutable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;issuer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://token.actions.githubusercontent.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;subject&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;repo:contoso@5544123/payments-api@821093847:ref:refs/heads/main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;audiences&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;api://AzureADTokenExchange&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For an Entra application, create the credential with Azure CLI:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;az ad app federated-credential create &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --id &amp;lt;application-object-id&amp;gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --parameters ./credential.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;--id&lt;/code&gt; value is the &lt;strong&gt;object ID&lt;/strong&gt; of the application, not the application (client) ID. Keep that distinction in mind when scripting the migration.&lt;/p&gt;
&lt;p&gt;Create one credential for each subject the workflow can present. For example, a workflow using both a production environment and a staging environment needs a trust entry for each intended subject unless you deliberately use a flexible federated identity credential.&lt;/p&gt;
&lt;p&gt;The example repository uses the following environment subjects:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# environment: dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;repo:builtwithcaffeine@141853123/bwc-github-federation-example@1380014691:environment:bwc-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# environment: prod
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;repo:builtwithcaffeine@141853123/bwc-github-federation-example@1380014691:environment:bwc-prod
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Those subjects allow the Entra application to distinguish between the development and production deployment environments. Create separate credentials when the environments should have different Azure permissions or different trust policies.&lt;/p&gt;
&lt;h2 id="flexible-federated-identity-credentials"&gt;Flexible federated identity credentials
&lt;/h2&gt;&lt;p&gt;If a workflow needs to support multiple branches or tags, a flexible federated identity credential can match the immutable repository prefix while still checking the repository ID separately:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;payments-api-repository-immutable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;issuer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://token.actions.githubusercontent.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claimsMatchingExpression&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;claims[&amp;#39;sub&amp;#39;] matches &amp;#39;repo:contoso@5544123/payments-api@821093847:*&amp;#39; and claims[&amp;#39;repository_id&amp;#39;] eq &amp;#39;821093847&amp;#39; and claims[&amp;#39;repository_owner_id&amp;#39;] eq &amp;#39;5544123&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;languageVersion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;audiences&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;api://AzureADTokenExchange&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The additional &lt;code&gt;repository_id&lt;/code&gt; and &lt;code&gt;repository_owner_id&lt;/code&gt; checks make the trust boundary explicit. Use a flexible credential only when the broader matching behaviour is intentional, and keep the expression limited to the repository and owner that should receive access.&lt;/p&gt;
&lt;h2 id="enable-immutable-subjects-in-github"&gt;Enable immutable subjects in GitHub
&lt;/h2&gt;&lt;p&gt;Existing repositories can opt in from the repository or organisation Actions OIDC settings. GitHub also exposes API controls and a preview endpoint for checking the resulting subject format.&lt;/p&gt;
&lt;p&gt;Before enabling the setting, compare the preview value with the subject in &lt;code&gt;credential.json&lt;/code&gt;. This catches common mistakes such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the application client ID instead of the GitHub repository ID&lt;/li&gt;
&lt;li&gt;Omitting the owner ID&lt;/li&gt;
&lt;li&gt;Using a branch subject when the workflow actually references an environment&lt;/li&gt;
&lt;li&gt;Forgetting that a pull request has a different subject context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After opt-in, newly issued OIDC tokens use the immutable subject. The workflow itself usually does not need a code change, but it must have permission to request an OIDC token. The following is the branch-based workflow from the example repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GitHub - Azure OIDC Example - Branch&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;oidc-auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;azure/login@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tenant-id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;client-id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subscription-id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Verify Azure login&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;az account show&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;id-token: write&lt;/code&gt; permission allows the job to request the GitHub token; it does not grant write access to the repository or Azure resources. Azure permissions still come from the role assignments on the application or managed identity.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;AZURE_TENANT_ID&lt;/code&gt;, &lt;code&gt;AZURE_CLIENT_ID&lt;/code&gt;, and &lt;code&gt;AZURE_SUBSCRIPTION_ID&lt;/code&gt; values identify the Azure tenant, application, and subscription. They are not client secrets. The OIDC token is what allows Microsoft Entra ID to authenticate the workflow without storing a long-lived credential in the repository.&lt;/p&gt;
&lt;h3 id="environment-based-workflow"&gt;Environment-based workflow
&lt;/h3&gt;&lt;p&gt;The second example selects an environment at dispatch time. The job&amp;rsquo;s &lt;code&gt;environment&lt;/code&gt; value changes the GitHub OIDC subject, so the Entra application can use separate credentials for &lt;code&gt;bwc-dev&lt;/code&gt; and &lt;code&gt;bwc-prod&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GitHub - Azure OIDC Example - Environment&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Select deployment environment&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;choice&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;bwc-dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;bwc-prod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;oidc-auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ inputs.environment }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;azure/login@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tenant-id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;client-id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subscription-id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Verify Azure login&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;az account show&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Because the job references the selected environment, GitHub emits an environment subject rather than a branch subject. Configure the &lt;code&gt;bwc-dev&lt;/code&gt; and &lt;code&gt;bwc-prod&lt;/code&gt; environments with the appropriate protection rules and secrets, then create matching federated credentials in Entra ID.&lt;/p&gt;
&lt;h2 id="validate-before-deleting-the-old-credential"&gt;Validate before deleting the old credential
&lt;/h2&gt;&lt;p&gt;Run the workflow after enabling immutable subjects and verify both authentication and authorisation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;azure/login&lt;/code&gt; step completes successfully.&lt;/li&gt;
&lt;li&gt;The workflow can perform the expected Azure operation.&lt;/li&gt;
&lt;li&gt;The sign-in or audit logs show the intended application and tenant.&lt;/li&gt;
&lt;li&gt;The workflow is using the expected branch, tag, or environment subject.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the login fails, compare the token claims with the federated identity credential. GitHub&amp;rsquo;s &lt;a class="link" href="https://github.com/github/actions-oidc-debugger" target="_blank" rel="noopener"
&gt;&lt;code&gt;actions-oidc-debugger&lt;/code&gt;&lt;/a&gt; action can help inspect the claims during troubleshooting. Do not leave a token containing sensitive claims in a public workflow log.&lt;/p&gt;
&lt;p&gt;Only after a successful run should you remove the old name-based credential:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;az ad app federated-credential delete &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --id &amp;lt;application-object-id&amp;gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --federated-credential-id &amp;lt;old-credential-id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Removing the old entry matters. Leaving it behind preserves the original mutable trust relationship and means the migration has not fully addressed the subject-recycling risk.&lt;/p&gt;
&lt;h2 id="operational-recommendations"&gt;Operational recommendations
&lt;/h2&gt;&lt;p&gt;Immutable subjects are a useful improvement, but they are not a replacement for a complete workload identity security model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use least privilege.&lt;/strong&gt; Give the Entra application only the Azure roles required by the deployment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scope the subject.&lt;/strong&gt; Prefer a specific environment or protected branch over every branch in a repository.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protect production environments.&lt;/strong&gt; Use GitHub Environment approvals and branch restrictions where appropriate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review credentials regularly.&lt;/strong&gt; Remove federated credentials for retired repositories and workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid long-lived secrets.&lt;/strong&gt; Once OIDC is working, remove obsolete service principal secrets from GitHub.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treat reusable workflows carefully.&lt;/strong&gt; If a reusable workflow is part of the trust boundary, consider matching its &lt;code&gt;job_workflow_ref&lt;/code&gt; as well.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document the IDs.&lt;/strong&gt; Keep the owner and repository IDs with the infrastructure code so a future migration does not depend on guesswork.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;GitHub Actions OIDC removes the need to store long-lived Azure credentials, but the claims in the token still define the security boundary. A subject based only on names is readable but vulnerable to renames, transfers, deletion, and reuse.&lt;/p&gt;
&lt;p&gt;Immutable subjects solve that specific problem by combining the familiar repository name with GitHub&amp;rsquo;s stable owner and repository IDs. For an existing Azure integration, the practical path is to add the immutable credential, opt the repository in, validate a real workflow, and then remove the old mutable credential.&lt;/p&gt;
&lt;p&gt;That small migration closes a subtle trust gap and leaves the federation relationship tied to the workload you actually intended to trust.&lt;/p&gt;
&lt;h2 id="references"&gt;References
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-github-immutable-subjects" target="_blank" rel="noopener"
&gt;Migrate GitHub Actions federated credentials to immutable subjects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-federated-credential-mutable-subjects" target="_blank" rel="noopener"
&gt;Mutable subjects in federated identity credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.github.com/en/actions/reference/security/oidc" target="_blank" rel="noopener"
&gt;GitHub OpenID Connect reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens/" target="_blank" rel="noopener"
&gt;Immutable subject claims for GitHub Actions OIDC tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/Azure/login" target="_blank" rel="noopener"
&gt;Azure Login action&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>