Outbound AAD B2B Discovery

Christopher Brumm
7 min readJun 18, 2021

In the last few months I was asked again and again in which other tenants users from a customer environment are active and unfortunately I had very few suggestions to find out. This time, however, it was about a tenant move and I had to deal with the topic a bit more intensively. In advance: My solution is not perfect but you can work with it ;-)

What is AAD B2B and why should I use it?

Before we get into the specific problem and its solution, I would like to give a short intro to B2B — imho one of the most powerful features of AAD. It enables collaboration across company boundaries and allows external access to apps connected in my tenant. Guests are usually invited by users or administrators and have their own lifecycle to manage.

A guest account can be thought of as a kind of shortcut to the real account in your home tenant. It can be interacted with (e.g. in Teams Channels), it can be assigned permissions and licenses in my tenant. However, the primary authentication of the account is always done in its home tenant.

From the perspective of an Azure AD administrator, there are two scenarios: Inbound and Outbound B2B!

Inbound B2B means that there are guests / externals in my tenant working on my resources.

For guests in my tenant, I have good options for control:

With the External Collaboration Settings I can control which rights a guest has in my directory and who can invite a guest. In special scenarios, I can also control from which DNS domains guests can be invited.

My recommendation for this is to keep the rights of a guest as low as possible on the one hand (Why? see here) and on the other hand to make the process of inviting a guest as low-threshold as possible.

In Conditional Access all guests can be selected as user type. So it is possible to exclude all guests from certain policies and to create policies for guests only. In combination with dynamic groups it is even possible to create policies for guests from certain domains or with static groups to create policies for especially privileged guests.

My recommendation is to create a separate set of rules in Conditional Access for guests in which at least MFA and, depending on the requirement, Terms of Use and Conditional Access App Control — and thus the use of the browser — are enforced.

Finally, with Access Reviews it is possible to manage the inventory of guests and get rid of orphaned guests. While it is possible to create an Access Review for all guests in teams, the feature is primarily designed with a focus on controlling access to resources (such as teams, groups, sites). Following this approach, Access Packages can be used directly to control not only a review but the complete control of permissions to these resources.

My recommendation is to engage with this topic early on, because experience shows that otherwise the number of guests and permissions will continue to grow and at some point it will be almost impossible to keep track of / control. There are for teams, for example, super concepts including automation from my friend Jakob.

Outbound B2B means that users from my tenant work on resources in other tenants and are guests there.

Unfortunately, it is not yet possible to detect in which remote tenant a user is a guest and it is not possible to control whether and in which tenants a user can be a guest. Additionally, all the control options described above are not applicable in this scenario.

The only — but somewhat outdated — approach to control is currently the AAD feature Tenant Restrictions, which requires a proxy server to set a header listing the allowed tenants.

Why do you want to know / control outbound B2B anyway?

Personally, I don’t think it’s a good idea to restrict outbound B2B, as that will only lead to more accounts and more shadow IT (but I’m sure there are other opinions 😉 )

However, there is a scenario in which one would like to know quite urgently in which other tenants the users are on the move: When moving the domains / users in another tenant.

After a cross-tenant migration, a guest must be re-invited because the relationship has become broken.

To prepare such a migration, my goal is to find the top scorers to inform the users and contact the ITs of the other tenants so that the guests can be re-invited on the day of the migration.

It is important at this point not to simply delete the guests and re-invite them, but to reset the guest so that the data and permissions attached to the guest are retained. For resetting the guest’s invitation, the remote tenant administrator has the options GUI or API (e.g. via Powershell).

To find an reset all guests from a specific domain you can extend my simple script for this.

Evaluation of the sign-in logs

To find out where my users are I started to look at the sign-in logs and was very happy about the new fields HomeTenantID and RessourceTenantId.

In order to be able to evaluate the logs in your own tenant well (and for various other reasons), the AAD logs should be sent to a log analytics workspace / Azure Monitor.

There, the sign-in logs (schema) can then be evaluated quite easily with KQL (Getting startedGetting Master):

SigninLogs
| where ResourceTenantId != HomeTenantId
| distinct UserPrincipalName, ResourceTenantId

Tenant Names and IDs

The result of my query is then a lot of Tenant IDs. Each tenant / each Azure Active Directory has a tenant ID and it is globally unique. For the own tenant there are several possibilities to determine this easily — for example on the start page of the AAD or via Powershell.

For foreign tenants a little more effort is required, but for a given domain at least these methods can be used to determine the appropriate tenant ID:

Unfortunately, however, there is no way (for privacy reasons, it seems) to perform this resolution backwards.

How can I do this for third-party tenants?

As I wrote in the introduction, my trigger was a review of a tenant that was to be replaced. This was a so-called unmanaged tenant not created by IT but by the users themselves. This can happen in the following situations:

  • Test licenses
  • Invitation to another tenant (or sharing)

In such a tenant, it is often not a given that AAD is integrated with a log analytics workspace and, depending on the planned approach, it may not be done anymore.

In this scenario, it is possible and useful to fall back on another more manual procedure.

  1. Export of data via GUI as JSON
  2. Import into Azure Data Explorer
  3. Evaluation via KQL

This was my first time working with Azure Data Explorer and I was very pleased with how easy an initial deployment was. Here is a guide on how to create it:

In the portal, the data can then be easily imported as JSON and start directly with the queries. I found it a bit hard to get used to the fact that the first letter of all field names after the import is always Lower Case.

I learned that it is really a good idea to start the ADX cluster only on demand 😂

Deeplinks and Tenant Branding

With a long list of tenant IDs I started asking around if anyone had any ideas how to find out which tenants are behind them. Fortunately, Jos Lieben got in touch relatively quickly on Twitter and recommended a Powershell snippet by John Seerden. He showed me that it is possible to call the deep link to the login page of a tenant with the tenant id. It looks like this:

https://login.microsoftonline.com/resourceTenantId/oauth2/authorize?client_id=12345

My query (Azure Data Explorer variant with lower case) now returns a descending list of tenant IDs sorted by number of users, including deep links to the tenants:

XXSignIns
| where resourceTenantId != homeTenantId
| distinct userPrincipalName, resourceTenantId
| summarize count() by resourceTenantId
| extend link = strcat("https://login.microsoftonline.com/", resourceTenantId, "/oauth2/authorize?client_id=12345")
| order by count_ desc
| project-reorder count_, resourceTenantId, link

This creates a table from which you can easily copy the link into an incognito session of the browser:

The branding of the tenants can then be very revealing and has solved the problem for me 😎

--

--