cd /news/ai-agents/minting-an-entra-agent-token-from-gi… · home › topics › ai-agents › article
[ARTICLE · art-145932] src=machinebrief.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Minting an Entra Agent Token From GitHub Actions: No Secret, No Certificate, Nothing Stored

A third installment in a series on Entra Agent ID shows how to mint an Entra agent token from a GitHub Actions workflow using GitHub-issued OIDC tokens instead of a stored secret or certificate, with the federated identity credential on the blueprint configured to trust issuer https://token.actions.githubusercontent.com. The two-leg exchange is unchanged: leg 1 authenticates as the blueprint and names the agent identity in fmi_path, and leg 2 authenticates as the agent identity using the token leg 1 returned, with only step 0 — the source of the client assertion — differing from the managed-identity case. The workflow requires id-token: write permission and an audience of api://AzureADTokenExchange matching the federated credential's audiences value, and the author notes that without that permission the runner's token request environment variables are simply absent and the request fails with no obvious indication of why.

by read5 min views3 publishedOct 6, 2026

Last Updated on October 6, 2026 by Editorial Team Author(s): suman saha Originally published on Towards AI. Minting an Entra Agent Token From GitHub Actions: No Secret, No Certificate, Nothing Stored Third in a series on Entra Agent ID. The first part covered the two-leg exchange and where the client assertion comes from, using a managed identity. The second covered the three configuration steps that have to be complete before the resulting token carries any authorisation. This part runs the same exchange from outside Azure, from a GitHub Actions workflow, and records what is different — which is less than one might expect in the protocol, and more than one might expect in the tooling. What changes, and what does not The exchange itself does not change. Leg 1 authenticates as the blueprint and names the agent identity in fmi_path; leg 2 authenticates as the agent identity using the token leg 1 returned. That is identical whether the workload runs on an Azure VM, in a GitHub-hosted runner, or anywhere else. What changes is step 0 — where the client assertion comes from. With a managed identity, the assertion is issued by the Azure IMDS endpoint and its iss is https://login.microsoftonline.com/{tenant}/v2.0. On GitHub Actions the assertion is issued by GitHub, its iss is https://token.actions.githubusercontent.com, and its sub describes the repository, the ref, and optionally the environment that produced it. That difference is the whole of it at the protocol level. The federated identity credential on the blueprint is configured with a different issuer and a different subject; everything downstream is the same. What also changes is what the tooling will do for you, which is where the time goes. Where the agent is, before we start One thing to settle first, because it is the question the rest of this reads against. The word “agent” carries three meanings here that this flow quietly collapses. The agent identity is a servicePrincipal object in Entra — durable, holding no credential, doing nothing on its own; it is a name to act under. The workload is the compute that authenticates as that identity and then does the work — here, a GitHub Actions job, which lives for the length of a run and is then gone. The agent in the sense most people mean — code that reasons and acts — is whatever runs holding the resulting token. The workflow below is the agent’s runtime for the length of the job; the agent identity is the durable name it borrows. There is no separate agent process in this demonstration — the token is the deliverable. Production agent code would run in the step after leg 2, using that token to call the resource. What binds a particular workflow to a particular agent identity is not a software artifact but two configuration values: the federated credential’s subject, which pins which workflow may authenticate, and the fmi_path in leg 1, which names which agent it acts as. This is also why GitHub Actions makes the point more sharply than a managed identity did. There, the compute had its own durable identity, and the separation between the workload and the identity it borrows stayed invisible. A runner that exists for ninety seconds cannot hide it. Step 0 — the assertion GitHub issues A workflow job with id-token: write permission can request an OIDC token from GitHub, scoped to an audience of its choosing: permissions: id-token: write contents: read curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=api%3A%2F%2FAzureADTokenExchange" Both environment variables are injected by the runner and exist only when id-token: write is granted. Without that permission they are absent and the request fails with no obvious indication of why — the variable is simply empty. The audience parameter must match the audiences value on the federated identity credential. For Entra workload identity federation that is api://AzureADTokenExchange, which is why it appears URL-encoded above. The resulting token is short-lived, issued on demand, and never stored. Decoded, its claims are: iss https://token.actions.githubusercontent.comsub repo:/:ref:refs/heads/aud api://AzureADTokenExchange The federated credential, and what its subject pins On the blueprint — not on the agent identity: POST /beta/applications/{blueprintObjectId}/federatedIdentityCredentialsContent-Type: application/json{ "name": "fic-github-", "issuer": "https://token.actions.githubusercontent.com", "subject": "repo:/:ref:refs/heads/", "audiences": ["api://AzureADTokenExchange"]} The subject must match the assertion’s sub exactly. There is no wildcard matching and no partial match: a credential pinned to refs/heads/main will not accept an assertion from refs/heads/feature/x, and the failure is a rejection at leg 1 rather than anything more descriptive. That strictness is the security property rather than an inconvenience. The subject is the only thing distinguishing this workload from any other that can reach the token endpoint. Pinned to a repository and a ref, it means a fork, a pull request from an untrusted contributor, or a different branch of the same repository cannot authenticate as this agent. Pinned only to repo:/, it means any branch can — including one a contributor created. For workflows that run on pull requests or in a deployment environment, GitHub’s subject format differs (pull_request, environment:). The practical approach is to print the subject the workflow actually presents and create the credential from that, rather than constructing it by hand: echo "repo:${GITHUB_REPOSITORY}:ref:${GITHUB_REF}" One caution on that subject format: an organisation can customise the OIDC subject claim GitHub issues, so on a repository where that has been done, sub is not what the template above predicts. Printing the actual value is not only convenient; it is the only reliable source. The two legs Leg 1 — authenticate as the blueprint, name the agent curl -X POST "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \ -d grant_type=client_credentials \ -d client_id="${BLUEPRINT_APP_ID}" \ -d scope='api://AzureADTokenExchange/.default' \ -d client_assertion_type='urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \ --data-urlencode client_assertion="${GITHUB_OIDC_TOKEN}" \ --data-urlencode fmi_path="${AGENT_APP_ID}" Note client_id is the blueprint's application ID. The agent identity appears only in fmi_path. The response is an exchange token whose client-identity claim is the blueprint and whose sub is a federated managed identity path ending in the agent's application ID: /eid1/c/pub/t//a// Leg 2 — authenticate as the agent curl -X POST "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \ -d grant_type=client_credentials \ -d client_id="${AGENT_APP_ID}" \ -d scope='https://graph.microsoft.com/.default' \ […]

── more in #ai-agents 4 stories · sorted by recency
── more on @entra agent id 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/minting-an-entra-age…] indexed:0 read:5min 2026-10-06 · —