Forum Discussion

klightspeed's avatar
klightspeed
Copper Contributor
Jul 03, 2026

[Bug] ExchangeOnlineManagement uses incorrect TenantId in requests

An interaction between the ExchangeOnlineManagement powershell module and Microsoft.Identity.Client 4.83.0+ results in the REST API requests sent by the ExchangeOnlineManagement powershell module using realm.onmicrosoft.com in place of the expected Guid TenantId. This results in the dreaded Expired or Invalid pagination request after fetching one page.

As discussed in https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/6093, Microsoft.Identity.Client.AuthenticationResult.TenantId can no longer be trusted to contain either null or the expected Guid TenantId, but instead will generally contain the realm.onmicrosoft.com realm name as of Microsoft.Identity.Client 4.83.0

As version 3.10.0 of the ExchangeOnlineManagement powershell module now depends on Microsoft.Identity.Client 4.83.1, this version of the powershell module can no longer retrieve groups with more than 1000 members or enumerate the groups, contacts, recipients, etc. in a domain that has more than 1000 of each without running into the dreaded Expired or Invalid pagination request.

In theory the following Lib.Harmony patch encodes a potential fix for this issue (tested locally using ExchangeOnlineFetch 3.10.0 in a dotnet 10 program):

using System.Reflection.Emit;
using HarmonyLib;

[HarmonyPatch("Microsoft.Exchange.Management.AdminApiProvider.Authentication.TokenProviderUtils", "GetTokenInformation")]
static class Patch_TokenProviderUtils_GetTokenInformation
{
    private static Harmony? _harmony = null;

    public static void PatchOnce()
    {
        var asms = AppDomain.CurrentDomain.GetAssemblies();

        if (_harmony is null
            && asms.FirstOrDefault(e => e.GetName().Name == "Microsoft.Exchange.Management.AdminApiProvider") is { } asm
            && asm.GetType("Microsoft.Exchange.Management.AdminApiProvider.Authentication.TokenProviderUtils") is { } type
            && type.GetMethod("GetTokenInformation") is { } method)
        {
            var harmony = new Harmony("com.github.klightspeed.exchangeonlinemanagement.tenantidfix");
            harmony.Patch(
                method,
                transpiler: new HarmonyMethod(typeof(Patch_TokenProviderUtils_GetTokenInformation), nameof(Transpiler))
            );
            _harmony = harmony;
        }
    }

    static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
    {
        var matcher = new CodeMatcher(instructions);

        var get_TenantId = AccessTools.PropertyGetter("Microsoft.Identity.Client.AuthenticationResult:TenantId");
        var get_Organization = AccessTools.PropertyGetter("Microsoft.Exchange.Management.AdminApiProvider.Authentication.TokenProviderContext:Organization");

        // patch the following code snippet:
        //
        // if (IsCertificateBasedConnection(context))
        // {
        //    var upn = GetUPNForAppOnlyBasedConnection(context);
        //    var tenantId = JwtSecurityTokenUtils.GetTenantId(tokenAcquisitionResult.AccessToken);
        //    return TokenInformation.Create(
        //        upn,
        //        authorizationHeader,
        //        tokenAcquisitionResult.TenantId ?? tenantId ?? context.Organization,
        //        tokenAcquisitionResult.ExpiresOn
        //    );
        // }
        //
        // to
        //
        // if (IsCertificateBasedConnection(context))
        // {
        //    var upn = GetUPNForAppOnlyBasedConnection(context);
        //    var tenantId = JwtSecurityTokenUtils.GetTenantId(tokenAcquisitionResult.AccessToken);
        //    return TokenInformation.Create(
        //        upn,
        //        authorizationHeader,
        //        PatchedTenantId(tokenAcquisitionResult.TenantId, context) ?? tenantId ?? context.Organization,
        //        tokenAcquisitionResult.ExpiresOn
        //    );
        // }

        matcher.MatchStartForward(
            new CodeMatch(CodeInstruction.LoadArgument(0)),
            CodeMatch.Calls(get_TenantId),
            new CodeMatch(OpCodes.Dup),
            new CodeMatch(OpCodes.Brtrue_S),
            new CodeMatch(OpCodes.Pop)
        );

        if (matcher.IsValid)
        {
            matcher.Advance(2);
            matcher.Insert(
                CodeInstruction.LoadArgument(1),
                new CodeInstruction(OpCodes.Call, get_Organization),
                CodeInstruction.Call(() => PatchedTenantId(default, default!))
            );
        }

        return matcher.InstructionEnumeration();
    }

    static string? PatchedTenantId(string? tenantId, string? organization)
        => tenantId == organization ? null : tenantId;
}

 

No RepliesBe the first to reply