The Missing Intro To Flexible Federated Identity Credentials
Table of Contents
Entra ID has this thing called “flexible Federated Identity Credentials”, or “flexible FIC” for short. You might be confused a bit by the very long name, but behind the term is a really powerful capability that I hope to cover well enough in this post. this is the missing guide - the document I wish I had when I first started exploring the concept of federated credentials.
What’s a credential #
While this blog post focuses on Entra ID, the concept of a “credential” is broadly applicable to any system that in some capacity implements an authentication and authorization mechanism. In layman terms, a credential is some kind of artifact that gives you or a program access to some resources - an API, a database, or a remote system (examples are, of course, not exhaustive). In Entra ID, credentials can take many shapes. You can have a user credential, that is - your user account that you use to sign in into your corporate systems. You can also have service principals, that act as identities for non-human entities, like APIs or, well, services that run in the cloud. You can also have managed identities, that are a variant of a service principal that is entirely managed by the Azure infrastructure, so you never have to worry about secrets such as certificates.
When you want to access a specific resource, say, an API hosted in the Azure cloud, you will need to have some kind of credential that is also accepted by said API. In most cases you can use existing credential types that I mentioned above, but more often than not you work in a rainforest of APIs, integrations, and extensions, rather than a well-curated walled garden with pre-defined interaction models. That brings along a whole bunch of complexity when it comes to authentication - if I am using Service A
that authenticates with GitHub credentials, how do I make sure that Service B
that is hosted in Azure and uses Entra ID trusts that Service A
can interact with it? This is where the concept of “federated credentials” comes in.
What’s a federated credential #
Let’s set aside the tech and infrastructure details for a second and think through a simpler analogy. Let’s say you decided to go and visit a massive amusement park, but tough luck - every ride requires its own ticket and has its own vending system. The tickets don’t quite look the same, they all have different shapes that don’t quite fit your wallet so you have to carry them in your hand everywhere you go. Extremely annoying. However, instead of you having to get a ticket for every ride, some company decided that they produce a wristband that proves that you already paid when you got into the amusement park. Got in line for a ride? They check your wristband, and you’re in!
Federated credentials are effectively that wristband. Instead of you having to store separate passwords for Azure in GitHub, or GitHub secrets in Azure, you can tell Entra ID (and by proxy - Azure) that you have vendor (such as GitHub’s OpenID Connect provider) that gave you a wristband that it can trust to give you access to all the rides (Azure resources). Granted, an amusement park of cloud resources is not as entertaining as a real amusement park, but you get the idea.
What’s an example use case #
Let’s snap back to the tech side of things. Let’s say I have a GitHub Action workflow that needs to be able to access some Azure Key Vault secret. What would you typically do for that? Why generate a secret key that you would store as a GitHub Action secret, of course. Then you would use the Azure CLI from within the GitHub Action workflow to log in and attempt to get the secret. But then someone needs to be able to also rotate the secrets of they leak somehow, and remember to update all of the GitHub repositories that used that secret. That’s hella cumbersome, even if partially automated.
The better way here is if we can convince Azure to trust GitHub’s very own JSON Web Tokens that are minted within the GitHub Action workflow, and then just pass those tokens to our infrastructure and have it handle the rest.

Yeah, um… I’m sorry to interject, but did you just say “GitHub’s very own JSON Web Tokens”? I didn’t know GitHub even issued JWTs - I thought all their tokens are of the gho_SOMETHINGHERE
format., which is not quite JWT.
You are correct! By default, GitHub doesn’t issue (or even use, in most cases) JWT tokens, with a few exceptions. One such exception is GitHub Actions, where you can get a JWT token issued by the GitHub OIDC endpoint. That’s right - you can get a full-blown JWT by adding a few lines of YAML into your action.
- name: Get ID Token
id: get_token
run: |
TOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" | jq -r '.value')
echo "JWT_TOKEN=$TOKEN" >> $GITHUB_ENV
This might be a big confusing because of a bunch of variables and cuneiform-looking arguments, so let me explain what’s happening here. I am acquiring the ID token for the current workflow run by issuing a GET
HTTP request against the GitHub Actions ID token request endpoint (captured as ACTIONS_ID_TOKEN_REQUEST_URL
). That endpoint is authenticated, therefore we’re passing an Authorization
header with a bearer token captured as ACTIONS_ID_TOKEN_REQUEST_TOKEN
. When invoking the endpoint, I am setting the target audience for my issued ID token (who’s going to be consuming it) to be the expected audience for Entra ID federated credentials (i.e., api://AzureADTokenExchange
). The output of this call (our token) is then extracted with the help of the jq
command that parses the output JSON and gives us just the encoded token.
The contents of TOKEN
can be very large, but if we decode the Base64 string, we will get something like this (extracted from a test repository I created for a demo - don’t worry, nothing secret in this decoded token):
{
"alg": "RS256",
"kid": "SOME_KID_VALUE",
"typ": "JWT",
"x5t": "SOME_X5T_VALUE"
}.{
"actor": "dend",
"actor_id": "1389609",
"aud": "api://AzureADTokenExchange",
"base_ref": "",
"event_name": "workflow_dispatch",
"exp": 1740915537,
"head_ref": "",
"iat": 1740893937,
"iss": "https://token.actions.githubusercontent.com",
"job_workflow_ref": "localden/test-github-fic/.github/workflows/tokentest.yml@refs/heads/main",
"job_workflow_sha": "7eec9b7fd975dacb531d3b53118a9f3b2276198b",
"jti": "SOME_JTI_VALUE",
"nbf": 1740893637,
"ref": "refs/heads/main",
"ref_protected": "false",
"ref_type": "branch",
"repository": "localden/test-github-fic",
"repository_id": "939058552",
"repository_owner": "localden",
"repository_owner_id": "53200638",
"repository_visibility": "private",
"run_attempt": "1",
"run_id": "13612508777",
"run_number": "6",
"runner_environment": "github-hosted",
"sha": "7eec9b7fd975dacb531d3b53118a9f3b2276198b",
"sub": "repo:localden/test-github-fic:ref:refs/heads/main",
"workflow": "Your Workflow",
"workflow_ref": "localden/test-github-fic/.github/workflows/tokentest.yml@refs/heads/main",
"workflow_sha": "7eec9b7fd975dacb531d3b53118a9f3b2276198b"
}.[Signature]
OK, so we have a JWT from GitHub, but how does it fit into the whole idea of federated credentials? Remember how I mentioned that we want to see if we can get Entra ID to trust this identity somehow to give us access to Azure Key Vault secrets? Let’s set that up.
For that, I am going to go ahead and register a new application in Entra ID. We will use this application as our “access entity” - that is, we will allow this application to access Azure resources in my subscription. You can do this in the Azure Portal by going to the Entra ID tab and clicking on New Registration.

With the application registered, we now can configure a federated credential. To do that, in our app registration we will go to Certificates & Secrets, then to Federated credentials, then click on Add credential and select Other issuer from the Federated credential scenario dropdown:

What we need to do now is configure our credential with the details of the GitHub OIDC-issued token. For the issuer, the value is pretty much fixed:
https://token.actions.githubusercontent.com
We see it in the iss
claim within the JWT example I provided earlier. This means that our intent here is to trust tokens coming from this issuer as if they are “native” to our Azure infrastructure. Next, we want to select the thing that this entire post started from - Claims matching expression (Preview). Unlike standard federated credentials, flexible federated credentials give you, you guessed it, flexibility in how to check what token you want to allow the use of and what tokens you don’t.
So, for example, for the Value field, we can put an expression like this:
claims['sub'] matches 'repo:localden/test-github-fic*'
In practice, this means that a token with the sub
claim that matches the prefix of what I have above (notice the wildcard) can be exchanged for an Entra ID token, but one that doesn’t - can’t. For JWTs issued inside GitHub Actions, the sub
claim contains a reference to the repository and the branch on which it’s operating.

Does it mean that I will be able to use any claim that I want to check against? For example, what if I want to use the actor
claim that is included in the token?
At this time, the claims that you can match against are restricted. Specifically for GitHub, you can use eq
and matches
operators in the expression for sub
and job_workflow_ref
claims. If you will try to set another one that is not yet supported, you will get an error, such as this one in Azure Portal:


OK, well, even if I don’t have all these claims available, I can probably use other issuers here, right? Say I want to use a Google JWT with flexible federated credentials. Is that possible?
Ah, not quite yet - flexible FIC supports GitHub, GitLab, and Terraform at this point in time, although in conversations with the product team they mentioned that they’re well aware of the demand for other issuers.
All things considered, I really like this capability because it allows for fine-grained control of who and under what conditions can access Azure resources from inside GitHub Actions without having to implement a bunch of custom scaffolding. Think about it - if you would use a standard secret that is stored inside GitHub for Azure resource access, all of this metadata needs to be obtained somehow for you to make the right access decision. By using flexible FIC, that is simplified by giving you direct access to interpreting the JWT coming in without having to worry about any extra credential artifact maintenance.
But anyway, we have this capability now - how do we actually use it? Well, for starters we need to make sure that the application we created earlier has access to the Azure Key Vault that we want to access. Because both live within the same Azure infrastructure, setting that up is a relatively frictionless process.
I open my Azure Key Vault, and go to Access Management (IAM) in the left sidebar. From there, I click on Role assignments, followed by Add and Add role assignment. Depending on your needs, you may want to choose a somewhat more restrictive assignment than what I use (Key Vault Administrator), mainly because you want to avoid the scenario where an application is over-permissioned. If you expect that the application only needs to read secrets, don’t let it write secrets.
When I am adding role assignments, I am able to pick User, group, or service principal, and from there - select the Entra ID application that I created earlier.

We now ensured that our application can access the Azure Key Vault. But still - this has nothing to do with GitHub JWTs. To get the ball rolling on the most important piece of the puzzle, I have this short Python snippet that demonstrates what’s happening:
import os
import jwt
import json
from azure.identity import (
ManagedIdentityCredential,
ClientAssertionCredential
)
from azure.keyvault.secrets import SecretClient
APP_CLIENT_ID = 'MY_APPLICATION_ID'
RESOURCE_TENANT_ID = 'MY_RESOURCE_TENANT_ID'
AUDIENCE = 'api://AzureADTokenExchange'
def get_github_token():
token = os.environ.get('GITHUB_TOKEN')
if not token:
raise ValueError("GITHUB_TOKEN is not set")
return token
credential = ClientAssertionCredential(
RESOURCE_TENANT_ID,
APP_CLIENT_ID,
lambda: get_github_token()
)
client = SecretClient(
vault_url='URL_TO_MY_KEY_VAULT',
credential=credential
)
secret_name = 'SuperSecretString'
retrieved_secret = client.get_secret(secret_name)
print(retrieved_secret.value)
Few things are happening here - let’s dissect them. First, we have the APP_CLIENT_ID
. This is the client ID for the Entra ID application we registered. Next, RESOURCE_TENANT_ID
. This is the tenant ID of the resource (in this case, Azure Key Vault). AUDIENCE
is fixed to api://AzureADTokenExchange
, as defined in the convention set up for federated credentials.
I also have a function called get_github_token
that gets the JWT from an environment variable that is set for the GitHub Actions workflow step:
- name: Run Python Script
env:
GITHUB_TOKEN: ${{ env.JWT_TOKEN }}
run: python fic.py
Remember that earlier I showed you the YAML snippet for the JWT token acquisition? This effectively sets the variable to GITHUB_TOKEN
within this step to be the JWT we acquired earlier. This is not to be confused with the traditional GITHUB_TOKEN
that exists inside GitHub Actions workflows - I reassigned it within this step only.
Back to the Python snippet, I am using the Azure Identity client library for Python to create a client assertion credential. This is, once again, a confusing term - what the heck is an “assertion”? A client assertion is a way for a client (such as our application) to authenticate itself with an identity provider without using a traditional client secret (like an opaque string) or password. In our case, we are using our GitHub JWT as an assertion. You can read more about assertions in RFC7521: Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants, but all you need to know for now is that we’re using the GitHub JWT as a valid assertion to talk to our application.
ClientAssertionCredential
does all the heavy lifting here. I use the artifact directly inside SecretClient
to get access to my Azure Key Vault instance and obtain the secret stored as SuperSecretString
. Once the workflow executes, I get this:

Neat! And I can do the same with other languages too - for example, Azure Identity client library is also available for Java, JavaScript, .NET, and even Go.
Conclusion #
Federated identity credentials are an easy way for you to avoid having to store secrets in your GitHub workloads to access Azure resources from inside GitHub Actions. It eliminates a whole class of vulnerabilities stemming from secret leakage/exposure by doing the unthinkable - eliminating secrets altogether.
And of course, if you are curious to learn more, you can also check out our blog post from November of last year where I talk about using FIC in the context of managed identities.