A deep dive into Azure AD Workload identity federation

Workload Identity Federation is a rather new concept in Azure AD, where service principals do not have keys in a directory, but in stead is federated to an external OpenID Connect (OIDC) provider, such as Okta, Ping, Github, GCP, AWS and – well – Azure AD.

A part of an earlier blogpost used a JWT in a client credential grant, signed by a KeyVault based certificate, to authenticate as an application. This time, this JWT will not be signed by a certificate, but instead by an OIDC provider.

The way I usually write blog posts is that I put up a hypothesis, or an idea of how something works, and test it step by step. This is no exception. Look at this drawing, that explains how this work:

As we can see here, in step 4 Azure AD will need to query the .wellknown/openid-configuration to validate that the JWT provided in step 3 is signed with a valid certificate. This means that in order to use this feature, our OIDC provider must be online. What if the OIDC provider was another Azure AD, and we used an MSI in the other Azure AD? This is actually something that is not possible today, authenticating with an MSI across Azure AD tenant boundary. Shouldn’t this feature make this work potentially? Let’s try!

Discalimer before you read the rest: No, it does not work. Federated flows does not work with AAD-issued identities… But maybe you’ll learn something anyway! 🙂

To test this out, I will be created a virtual machine, with a system assigned identity (which is a Managed Service Identity (MSI)) in Tenant A, using the MSI to access resouces in Tenant B.

VM up and running and can get access token for its own tenant at least:

$url = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com/"
$token = Invoke-RestMethod $url -Method GET -Headers @{Metadata = "true"}

$token.access_token

We have the following MSI available in our Azure AD – Tenant A:

Now, over to Tenant B. We create a “New app registration” and end up here:

And trying to add another credential, we get this silly informational message:

That’s not really what we want, and also not what the documentation states:

However, in the next steps section, this neat little doc is linked, that documents how to add these credentials through Graph… So, let’s do that then!

Looking at this documentation shows us that we should only need to set issuer and subject in a federatedIdentityCredential object:

First try on guessing the Graph endpoint:

Let’s try to POST a new credential!

Close but no cigar

Second try:

There we go, after adding some more parameters, we have successfully added a federated identity credential to our app:

I don’t yet know whether the issuer is correct though, but we can now try things! We set the audience to api://AzureADTokenExchange, so I guess that is the resource we need to request:

$url = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://AzureADTokenExchange"
$token = Invoke-RestMethod $url -Method GET -Headers @{Metadata = "true"}

$token.access_token

And indeed this gives me an access token signed by Tenant A – which is a JWT that I hopefully should be able to provide to Tenant B, in order to “become” the other service principal:

Decoding the JWT, I see that a) the audience is not the url I set, and b) the issuer is something else too:

Trying to add that audience and issuer gives us a brand new error message!

It actually seems that it checks whether my provided subject value exists in the tenant or something!? (Hey, that sounds like something I can abuse later).

The error as text, just in case someone googles it. Currently yields 0 results:

For the specified issuer, the subject must be the id of a servicePrincipal that exists in the tenant. paramName: FederatedIdentityCredential.Subject, paramValue: 0f02d3db-5df0-41dd-98ad-31e6f9e3c02a, objectType: Microsoft.Online.DirectoryServices.Application

Here I am really stuck. Could it be that it errors out because it is an MSI? I tried a regular app registration – not of type MSI – but same error there too. Tried the app id, the object id of the service principal and the object id of the app registration itself. No luck.

So, it really seems that whenever the issuer in the format of https://login.microsoftonline.com/REMOTE_TENANT_ID/v2.0, there is a lookup happening, that is supposed to verify that the subject exists in that remote tenant.

Perhaps our app must first be assigned to this AzureADTokenExchange api? We can search for that app:

Ok, let’s create it?

Wow, found something! Here is the whole service principal:

DeletionTimestamp                  :
ObjectId                           : 1124a216-1ae7-4011-b339-1f1b656e0fdc
ObjectType                         : ServicePrincipal
AccountEnabled                     : true
AddIns                             : {}
AlternativeNames                   : {}
AppDisplayName                     : AAD Token Exchange Endpoint
AppId                              : fb60f99c-7a34-4190-8149-302f77469936
AppOwnerTenantId                   : f8cdef31-a31e-4b4a-93e4-5f571e91255a
AppRoleAssignmentRequired          : False
AppRoles                           : {}
DisplayName                        : AAD Token Exchange Endpoint
ErrorUrl                           :
Homepage                           :
KeyCredentials                     : {}
LogoutUrl                          :
Oauth2Permissions                  : {}
PasswordCredentials                : {}
PreferredTokenSigningKeyThumbprint :
PublisherName                      : Microsoft Services
ReplyUrls                          : {}
SamlMetadataUrl                    :
ServicePrincipalNames              : {fb60f99c-7a34-4190-8149-302f77469936, api://AzureADTokenExchange}
ServicePrincipalType               : Application
Tags                               : {}

What is interesting here is that there are no OAuth2 permissions, meaning no scopes to consent to. Not like “consent to user.read.all” or something. Could it be that this service principal is used to look up my application in Tenant A, from Tenant B? Farfetched, but hey..

New-AzureADServiceAppRoleAssignment -Id 9a5d68dd-52b0-4cc2-bd40-abcf44ac3a30 -PrincipalId $sp.ObjectId -ResourceId $g.Objectid -ObjectId $sp.ObjectId

I might be going insane, as I just added the Graph permission application.read.all to the “AAD Token Exchange Endpoint” service principal. Still does not work.

What I have gotten to work, is adding a federated credential from the same tenant (Tenant B):

This would essentially enable impersonation from an MSI to a service principal within the same tenant. Useful, but not fully what I wanted! I guess we will continue with that approach first then, and hopefully update this post later when support from cross tenant is available.

$original_app_clientid = "80945e65-e387-4907-b0e4-67a4ee2c4895"
$original_app_secret = "4K47Q~Xd2jC9y5MHr7y1REEn8YE1CjojOpC_f"
$original_app_tenant = "d8c61e7e-b160-44f0-a098-a6dadd67a495"

# Get JWT from original OIDC provider (Azure AD)
$uri = "https://login.microsoftonline.com/{0}/oauth2/token" -f $original_app_tenant
$body = "resource=api://AzureADTokenExchange&client_id=$($original_app_clientid)&grant_type=client_credentials&client_secret={0}" -f [System.Net.WebUtility]::UrlEncode($original_app_secret)
$tokenexchange = Invoke-RestMethod $uri -Body $body -ContentType "application/x-www-form-urlencoded" -ErrorAction SilentlyContinue


$federated_app_clientid = "7492e617-4e2f-4a88-8174-f4bd4968654d"
$federated_app_tenant = "d8c61e7e-b160-44f0-a098-a6dadd67a495"

$uri = "https://login.microsoftonline.com/{0}/oauth2/token" -f $federated_app_tenant
$body = "resource=https://graph.microsoft.com/.default&client_id=$($federated_app_clientid)&grant_type=client_credentials&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={0}" -f [System.Net.WebUtility]::UrlEncode($tokenexchange.access_token)
$federated_app_token = Invoke-RestMethod $uri -Body $body -ContentType "application/x-www-form-urlencoded" -ErrorAction SilentlyContinue

Bang…

AADSTS700222: AAD-issued tokens may not be used for federated identity flows.

Really? Well, I guess some times things doesn’t work out as expected. At least we learned a bit about the different parts of federated workflow identities, and perhaps next time I’ll write a blog post about federating workflow identities from AWS or GCP.

3 thoughts on “A deep dive into Azure AD Workload identity federation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s