Going for Broke(ring) – Offensive Walkthrough for Nested App Authentication

Aug 13 2025
Share
By: Hope Walker • 19 min read

TL;DR: Microsoft uses nested app authentication (NAA) for many applications. Access and refresh tokens for select applications, such as administrator portals, can be exchanged for tokens to other applications with a brokered request to authentication endpoints. 

Introduction

Starting in October 2024, Microsoft made NAA generally available with the goal to “[provide] better security and greater flexibility in app architecture, enabling the creation of rich, client-driven applications.” NAA is available for many Microsoft applications and can be integrated into custom applications as well. The idea is that certain applications which a user is already authenticated to can broker authentication requests to other applications to improve security and user experience.  While the official name is nested app authentication or NAA, SpecterOps and other researchers gave it an alternative name: BroCI, an abbreviation for “brokered client IDs” as this functions similar to family of client IDs (FOCI). Since NAA is an acronym used commonly in SCCM tradecraft, you may see instances of BroCI used instead to avoid confusion. For this post, I will be using NAA primarily, but keep in mind that BroCI is the same thing. 

Great work has already been done to enumerate and operationalize NAA for offensive purposes. The goal of this blog is to cover details about how an operator can use NAA to pivot to additional resources in Azure and Entra ID. We will cover the details of how to create token requests and go through some examples of exchanging tokens to access resources by hand and using tools such as EntraTokenAid, roadtx, and SpecterOp’s own Maestro. 

Other Research 

In January 2025, SpecterOps hosted a hackathon where Chris Thompson, Darrius Robinson, Costa Papadatos, and myself first came across brokering while looking at a token delegation endpoint. At the time, we designated this BroCI for brokered client IDs, since it had similarities to FOCI. Chris Thompson implemented the process for brokered requests into Maestro so we could easily test this process more. 

Information at that time was scarce; however, since then, Dirk-jan Mollema  implemented brokering into roadtx. Honestly, this was very encouraging for us even though we were not making much progress. If Dirk-jan was looking into it, then there had to be something cool. Dirk-jan, along with Fabian Bader, also recently released entrascopes where more information about applications and permissions are in a searchable format. They presented this information recently at TROOPERS where they also informally referenced the method as BroCI; so this may be a term that comes up related to NAA as more attention is drawn to it. I want to take a moment to give a huge thank you to Dirk-jan for chatting with me and helping me troubleshoot ROADtools while I wrote this blog. 

Following our initial internal discovery, in February 2025, zh54321 did a phenomenal job enumerating applications, extensions, and default permissions related to brokered NAA requests in the repository GraphPreConsentExplorer. This project has a web GUI that loads a YAML file with information about applications and copyable commands to use with EntraTokenAid

So, for this blog, I am not revealing anything new as these folks did a great job with a lot of the work. Instead, I am going to dig into how operators can leverage NAA in a security assessment to access resources.

Use Cases

NAA can be very useful in offensive engagements when trying to gain access to different resources. To access many applications and extensions from the portal, new tokens are needed, which may not already be in the user cache. For example, say you want to activate a PIM role for the user, but the user has not accessed PIM for an extended period. With NAA, you can use a refresh token from the Azure Portal to access PIM as the user without needing to wait for Azure to issue a token. 

Multi-factor authentication (MFA) claims to also carry over in brokered NAA requests. So, in a scenario where you may have tokens and the plaintext username and password but no way to complete an MFA prompt, you can use NAA with administrator portal tokens that already have MFA claims. Those claims will carry over to other tokens and satisfy MFA requirements. Since Microsoft instantiated conditional access policies (CAPs) in most tenants that require MFA to access administrator portals, this can be useful for satisfying those requirements.

For this blog, we will look at four example scenarios for how brokering NAA can be used in an offensive engagement. The following scenarios will cover:

  1. Building a request by hand to get CAPs
  2. Using EntraTokenAid to activate a PIM role
  3. Using roadtx to get a Key Vault secret
  4. Using Maestro to get Intune devices

For all of the scenarios, we will assume that the refresh token is already in our possession and the target user has the necessary permissions to perform the action. 

Scenario 1: Building a Request by Hand to Get CAPs

Before we jump into examples, I want to spend a little time talking about the components needed for brokering NAA. The first thing we will dive into is understanding the parameters required for a NAA request. First and foremost, we will need an access or refresh token that one of the administrator portals which conducts the brokering issues. To keep the blog focused, we are only going to include refresh tokens from Azure Portal. You can also use other applications such as Intune and M365 administrator portals for brokering.

There are a few parameters for a NAA request which include: 

  • grant_type 
  • A token, either: refresh_token or access_token 
  • redirect_uri
  • client_id
  • scope
  • brk_client_id 
  • brk_redirect_uri

For our example, we will start with just a regular token issued after logging into the Azure Portal and then we will target the ADIbizaUX to get the CAPs. The reason we want to target this application is because when the user interacts with the Portal, this is the actual application being used through the browser. Using roadtx, we will start by looking at the current token. 

A screenshot of a computer program

AI-generated content may be incorrect.

We can see the appid is Azure Portal and the aud is "https://management.core.windows.net/" for the current token. If we try to use this token to get CAPs, we will get an unauthorized message like this one:

A screen shot of a computer screen

AI-generated content may be incorrect.

So, instead, let’s target the ADIbizaUX application, as this will allow us to access the CAPs. We are going to start building out the pieces of our request, starting with the endpoint. The request needs to go to login.microsoftonline.com and include the tenant id. We are going to build out the request by hand to use with curl so the request will be a POST request to the token endpoint:

https://login.microsoftonline.com/<tenant ID>/oauth2/v2.0/token

Typically, these requests have broker information included in the URL; however, since we’re building it by hand, we will slim the request down to only what is necessary. In addition to the URL, we need to build out our headers, so let’s go ahead and get those out of the way:

  • Content-Type: application/x-www-form-urlencoded;charset=utf-8
    • This header tells the server what type of content is in the payload
  • User-Agent:
    • This header can be anything, but the request will fail without a user agent string
  • Origin: https://portal.azure.com
    • Again, this header can be anything but needs to be included

Next, we need to build our payload for the request. This is where we include the parameters for NAA and what we want brokered. As with other areas, there are additional parameters typically included in these requests, but we are keeping it to the bare minimum of what is needed. If detection or stealth is a portion of the assessment, we would want the requests to match information as closely as possible to normal requests. 

To get tokens for different applications, we will indicate which one we want to target. This is where the client_id parameter comes in. While it is seemingly straightforward, there are a few nuances we should be aware of. If the client ID is not included in our request, it will result in tokens for MS Graph so we will have access to MS Graph resources. Additionally, the application we target must be enabled for the tenant. If it is not, then we will not be able to acquire tokens. Lastly, the application must be in the list of applications that can be brokered. We can check this in GraphPreConsentExplorer by filtering for “Brk Refresh Flow” or here in entrascopes. The format for the client ID will look like this:

client_id=74658136-14ec-4630-ad9b-26e160ff0fc6

This is the client ID for the ADIbizaUX. This is a great one to use because it has a ton of Graph API permissions. This is pretty typical and if we were to watch the flow of a user navigating the portal, this is what would be used as well. If we look at it in GraphPreConsentExplorer, we can see there are a lot of permissions included there: 

A screenshot of a computer

AI-generated content may be incorrect.

Next is the redirect URI. This is the redirect for the broker. The format for this starts with brk then the application client ID GUID, followed by the URL for the portal or application.  Since we are using the Azure Portal for our broker it will look like this:  

redirect_uri=brk-c44b4083-3bb0-49c1-b47d-974e53cbdf3c://portal.azure.com

This is the GUID for the Azure Portal application appended with "://" and the URL for the portal.

Next, we need to specify the scopes. This is the scopes we want to request for the application we are targeting. If a scope is not specified, then tokens are issued with the default scopes for the application. In the request, the scope will look like this:

scope=https://graph.microsoft.com/.default

This is the scope we want to request for the token. To keep it simple for this request, we will remove everything except for the default. Additional scopes can be added with a space between each scope. If we want specific scopes, we can add them here; I recommend using quotes to ensure it is interpreted correctly. 

Next, we need to tell the server what type of token we are going to redeem. In this example, let’s stick with a refresh token. This is what is typically used and probably a better choice depending on our situations since refresh tokens have a much longer lifetime than access tokens and can be reused more. The grant type will take this format for refresh token and access token respectively: 

grant_type=refresh_token

grant_type=access_token

In our examples, we will use the first option. This will tell the server that we are providing a refresh token. So, of course, the next parameter will be the token we told the server to use.

refresh_token=<refresh token contents>

Then we finish it out by including our brk_client_id and brk_redirect_uri. 

brk_client_id=c44b4083-3bb0-49c1-b47d-974e53cbdf3c

brk_redirect_uri=https://portal.azure.com/

The brk_client_id is going to be the Azure Portal, because that is the application we want to broker our request. The brk_redirect_uri is the URL for the Azure Portal since that handles our brokering.

In the end, our payload should look like this: 

client_id=74658136-14ec-4630-ad9b-26e160ff0fc6&redirect_uri=brk-c44b4083-3bb0-49c1-b47d-974e53cbdf3c://portal.azure.com&scope=https://graph.microsoft.com/.default&grant_type=refresh_token&refresh_token=<refresh token contents>&brk_client_id=c44b4083-3bb0-49c1-b47d-974e53cbdf3c&brk_redirect_uri=https://portal.azure.com/

Assuming all parameters are correct, we should submit this and receive a response with access, refresh, and ID tokens. Now let’s look at using the newly minted token. 

A screen shot of a computer program

AI-generated content may be incorrect.

Looks good to me. Let’s try to use it now. We are going to request the CAPs for the tenant and since our scope was graph.microsoft.com and in the token, we can see the audience ("aud") is also graph, that is where we will send the request. The request will be a POST request to the /v1.0/$batch endpoint. Here is what it will look like: 

https://graph.microsoft.com/v1.0/$batch

Then we need to include our new token. 

Authorization: Bearer <access token>

This will be the access token with all of the permissions we just requested for our token. The final header will be to tell the server what content type we are sending in the payload. 

Content-Type: application/json

We need to have our payload, which is where we make the request for the tenant CAPs. This is also very slimmed down to only what is necessary. 

{"requests":[{"id":"1","method":"GET","url":"/identity/conditionalAccess/policies"}]}

The ID here is a request ID. This tracks the request for troubleshooting or whatever. I’m being flippant here because we just need to have the parameter for the request to work. We don’t really care about tracking the requests. Maybe there is something that would show up in the logs but, for now, we won’t worry about setting this to something valid. Using this with curl, our command will look like this:

curl.exe --path-as-is -i -s -k -X 'POST' -H 'Authorization: Bearer <access_token>’ -H 'Content-Type: application/json' --data-binary '{\"requests\":[{\"id\":\"1\",\"method\":\"GET\",\"url\":\"/identity/conditionalAccess/policies"}]}' 'https://graph.microsoft.com/v1.0/$batch'

A screen shot of a computer screen

AI-generated content may be incorrect.

Our output will be a JSON format of the CAPs. Although not the easiest to read as GUIDs are displayed, it is a way to acquire the CAPs from the tenant. 

Scenario 2: Using EntraTokenAid to Activate a PIM Role 

We just walked through a lot of information, so you might be asking yourself if there is a faster or easier way to do it. Luckily, there is. Now that we have covered an in-depth example, let’s look at some tooling that will make this process easier.

For this scenario, we’ll use EntraTokenAid to mint our tokens then use the access token to activate a PIM role for our user. GraphPreConsentExplorer shines again here. When we search for PIM in the interface, we can not only get the information about the application, but also the EntraTokenAid command is easily copied out with all of the necessary options

A screenshot of a computer

AI-generated content may be incorrect.

To use this directly in PowerShell, the refresh token needs to be stored in the $PortalArmToken variable. The command will return the token to the $tokens variable. For us to use the token, we need to convert the access token to a secure string, then it becomes usable with the Connect-MgGraph PowerShell command to authenticate to the tenant. The process should look like this: 

A computer screen shot of a blue screen

AI-generated content may be incorrect.

$tokens = Invoke-Refresh -RefreshToken $PortalArmToken -ClientID '50aaa389-5a33-4f1a-91d7-2c45ecd8dac8' -BrkClientId 'c44b4083-3bb0-49c1-b47d-974e53cbdf3c' -RedirectUri 'brk-c44b4083-3bb0-49c1-b47d-974e53cbdf3c://portal.azure.com' -Origin 'https://portal.azure.com'

$access = ConvertTo-SecureString -string $tokens.access_token -AsPlainText -Force

Connect-MgGraph -AccessToken $access

Then we need to build the request body to activate our role. At this point, we will assume we have already enumerated the eligible roles and the information we need to activate the role. 

A computer screen with a blue background

AI-generated content may be incorrect.

Then we use the New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest command with our parameters to request role activation. 

A screenshot of a computer screen

AI-generated content may be incorrect.

If we check in the portal, we can now see that our role is active:

A screenshot of a computer

AI-generated content may be incorrect.

Although our role is active, the current token we minted earlier does not have that information unless additional scopes were requested. We will need to acquire a new token to use the role we activated.

Scenario 3: Using Roadtx to Get a Key Vault Secret

Don’t worry. I’m not just going to gloss over MFA. Many tenants these days have MFA requirements configured in their CAPs (even if they didn’t want it), so it is important to at least touch on. The cool thing with brokering is that MFA claims carry over from one token to the other. For this next example, let’s use roadtx from ROADtools to request a new token and get a secret from Key Vault.

To start, let us assume we have the refresh token we need.  We are going to request tokens which will let us get a secret out of a key vault, which we will assume we have already enumerated and identified. For this, we have a CAP which requires the user to have MFA to access all applications. 

The first command will be with roadtx to get new tokens. To use NAA and brokering, we need to use the refreshtokento command and provide the correct options. 

  • --refresh-token
    • The refresh token we acquired
  • -s "https://vault.azure.net/.default openid profile offline_access"
    • Scope for the request
    • The URL will be the aud in our token
    • The following options are the scopes we are requesting in our token
  • --broker-client "c44b4083-3bb0-49c1-b47d-974e53cbdf3c"
    • This is going to be the application doing the brokering, in this case, Azure Portal app ID
  • -c "3686488a-04fc-4d8a-b967-61f98ec41efe"
    • This is the client ID we are going to target
    • This is the Microsoft Azure Key Vault portal extension 
  • --broker-redirect-url "brk-c44b4083-3bb0-49c1-b47d-974e53cbdf3c://portal.azure.com"
    • This is our redirect_uri option from when we built the request by hand and starts with "brk"
  • --origin https://portal.azure.com
    • This is needed to show that is a cross-origin request

Enough talk. Send it!

A screenshot of a computer screen

AI-generated content may be incorrect.

Bam! New tokens. Let’s check if our MFA carried over.

A screenshot of a computer program

AI-generated content may be incorrect.

We can check this in the amr, or Authentication Method Reference, of the token. This token has both pwd and mfa, which means our MFA carried over. 

We will pull out the access token and then we can make our request to the Key Vault. For this, we are just going to make a direct request to the Key Vault endpoint for the secret we want. Since this is a resource with its own URL, making the request is relatively straightforward. We are going to use curl again to show the required parameters. For this request, the bare minimum of what we need for headers is going to be: 

  • Authorization: Bearer <access_token>
  • User-Agent: <value>
  • Origin: https://portal.azure.com

One small note: for the request to work, if we are getting the copyable information for the key vault endpoint, we will need to add the API version to the end like this: ?api-version=7.4. Now we are all set to get secrets. 

A computer screen shot of a blue screen

AI-generated content may be incorrect.

Success! We have retrieved the secret from the key vault. 

Scenario 4: Using Maestro to Get Intune Devices

For this scenario, let us take a look at using Maestro. If you are not familiar with Maestro, you can read the release blog here. For this example, Maestro is a great option because it has a ton of Intune functionality built in. The author, Chris Thompson, has also included brokering capabilities into Maestro. This makes it as easy as two commands to go from an Azure Portal refresh token to listing Intune devices in a tenant. 

To start, we will take our Azure Portal refresh token and use it with Maestro to get a new token. Since we are looking to get Intune information, we are going to target the Microsoft Intune portal extension. We can find information about this extension in GraphPreConsentExplorer. 

This will help as reference for the options we need for the request. For Maestro, we will need the following flags: 

  • get
    • The get command for Maestro
  • access-token
    • Used to tell Maestro to get a new access token
    • A refresh token is acquired too
  • --refresh-token
    • Our Azure Portal refresh token
  • --broker
    • Indicates that this will be a brokered NAA request
  • --brk-client-id
    •  Client ID for the Azure Portal which will be our broker
    • The value will be the GUID "c44b4083-3bb0-49c1-b47d-974e53cbdf3c"
  • --client-id or -c
    • Client ID for the Microsoft Intune portal extension we want the token for
    • Since we are targeting Microsoft Intune extension portal our client ID will be "5926fc8e-304e-4f59-8bed-58ca97cc39a4"
  • --scope or -s
    • Scopes for the token
    • To keep it simple, we are just going to use ".default" but additional scopes can be added
  • --token-method or -m
    • Authentication method for access tokens
    • Options:
      • 0: /oauth2/v2.0/token
      • 1: /api/DelegationToken
      • 2: MSAL (default: 0)
    • As we learned earlier, we want option 0 for our endpoint
  • --resource or -r
    • Audience for the token
    • Since we are targeting a portal extension, we will use "https://portal.azure.com"
  • --tenant-id
    • Tenant ID

For our request, the command with Maestro will look like this: 

.\Maestro.exe get access-token --refresh-token $RefToken --broker --brk-client-id "c44b4083-3bb0-49c1-b47d-974e53cbdf3c" -c "5926fc8e-304e-4f59-8bed-58ca97cc39a4" -s ".default" -m "0" -r 'https://portal.azure.com' --tenant-id "6c12b0b0-b2cc-4a73-8252-0b94bfca2145"

After we get the token, we can save the access token in a variable and easily use it with Maestro. Next, we will save the access token into a variable and use it with Maestro. We are going to get all of the devices in Intune with the following command: 

.\Maestro.exe get intune devices --access-token $accesstoken

Now we have all of our Intune devices with just two commands in Maestro starting from an Azure Portal refresh token. While this was all done through the command line, one great feature for Maestro is that Chris designed it to be used through command and control (C2) agents. This is especially helpful when CAP restrictions or stealth are a requirement in assessments.

Conclusion

Nested app authentication can open more opportunities for resource access by using the brokering process Microsoft implemented. Internally, we have been referring to this as brokered client IDs or BroCI since it works similarly to FOCI. With work on tooling by other researchers, this method is ready to use in a variety of applications.