1
votes

I have succesfully setup a multi tenant application. For now, I am able to authenticate the user and use tokens to access other resources. (Microsoft Graph & Microsoft AD Graph)

Now I want to get B2B working. Current flow: - User signs in - AuthorizationCodeReceived gets the acquires the token (via $commonAuthority endpoint) - When requesting a token for the Ad Graph, I am using the $tenantAuthority

This works perfectly when $tenantAuthority is the same tenant authority as the one where the account was created in.

However, if I login with another user (from another tenant, given trust to the actual tenant) and use $tenantAuthority = trusted authority, then I always the following error: Failed the refresh token: AADSTS65001: The user or administrator has not consented to use the application with ID

If I change $tenantAuthority to the 'source' tenant authority where the user was created in, everything works fine.

Any help would be greatly appreciated.

Update: Code sample

App has two tenants (tenantA en tenantB) and I will use a user from tenantB with tenantA given a trust to this user.

AuthorizationCodeReceived = async context =>
                    {
                        TenantContext.TenantId = "someguid";
                        var tenantId =
                            TenantContext.TenantId;

                        // get token cache via func, because the userid is only known at runtime
                        var getTokenCache = container.Resolve<Func<string, TokenCache>>();
                        var userId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.ObjectIdentifier).Value;
                        var tokenCache = getTokenCache(userId);
                        var authenticationContext = new AuthenticationContext($"{configuration.Authority}",
                            tokenCache);

                        await authenticationContext.AcquireTokenByAuthorizationCodeAsync(
                            context.Code,
                            new Uri(context.Request.Uri.GetLeftPart(UriPartial.Authority)),
                            new ClientCredential(configuration.ClientId, configuration.ClientSecret),
                            configuration.GraphResourceId);
                    }

This code works perfectly. Login in with a user from both tenants works perfectly.

But when I need the Graph Service Client or ActiveDirectoryClient, I need to obtain access tokens to been able to address an api for a certain tenant. I retrieve the access tokens like this:

public IGraphServiceClient CreateGraphServiceClient()
    {
        var client = new GraphServiceClient(
            new DelegateAuthenticationProvider(
                async requestMessage =>
                {
                    Logger.Debug("Retrieving authentication token to use in Microsoft Graph.");

                    string token;
                    var currentUserHomeTenantId = TenantContext.TenantId;
                    var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
                    var authenticationContext =
                        new AuthenticationContext($"{_configuration.TenantAuthorityPrefix}{currentUserHomeTenantId}",
                            _tokenCacheFactoryMethod(currentUserObjectId));
                    var clientCredential = new ClientCredential(_configuration.ClientId, _configuration.ClientSecret);

                    try
                    {
                        token = await GetTokenSilently(authenticationContext, _configuration.GraphResourceId, currentUserObjectId);
                    }
                    catch (AdalSilentTokenAcquisitionException e)
                    {
                        Logger.Error("Failed to retrieve authentication token silently, trying to refresh the token.", e);
                        var result = await authenticationContext.AcquireTokenAsync(_configuration.GraphResourceId, clientCredential);
                        token = result.AccessToken;
                    }

                    requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderKeys.Bearer, token);
                }));

        return client;
    }


    public IActiveDirectoryClient CreateAdClient()
    {
        var currentUserHomeTenantId = TenantContext.TenantId;
        var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
        var graphServiceUrl = $"{_configuration.AdGraphResourceId}/{currentUserHomeTenantId}";
        var tokenCache = _tokenCacheFactoryMethod(currentUserObjectId);

        var client = new ActiveDirectoryClient(new Uri(graphServiceUrl),
            () => GetTokenSilently(
                new AuthenticationContext(
                    $"{_configuration.TenantAuthorityPrefix}{ClaimsPrincipal.Current.FindFirst(ClaimTypes.TenantId).Value}", tokenCache
                ),
                _configuration.AdGraphResourceId, currentUserObjectId
            ));

        return client;
    }

When I do a request with one of the two client SDK's, I got the following error: Failed the refresh token: AADSTS65001: The user or administrator has not consented to use the application with ID.

2
Did you see the consent page when you login using the accounts from other tenant? If not would you mind share the piece of code how about login?Fei Xue - MSFT

2 Answers

1
votes

Changing the catch method when retrieving the Token did the trick:

if(e.ErrorCode == "failed_to_acquire_token_silently")
{
    HttpContext.Current.Response.Redirect(authenticationContext.GetAuthorizationRequestUrlAsync(resourceId, _configuration.ClientId, new Uri(currentUrl),
                        new UserIdentifier(currentUserId, UserIdentifierType.UniqueId), string.Empty);
}
0
votes

I don't see that you mention that so: in a B2B collaboration you've to invite user from other tenant first. The steps are like that:

  • invite and authorize a set of external users by uploading a comma-separated values - CSV file
  • Invitation will be send to external users.
  • The invited user will either sign in to an existing work account with Microsoft (managed in Azure AD), or get a new work account in Azure AD.
  • After signed in, user will be redirected to the app that was shared with them

That works perfectly in my case.

Regarding some problems which I've detect:

Trailing "/" at the end of the active directory resource - try to remove it as this may cause problems. Bellow you will find some code to get authentication headers:

string aadTenant = WebServiceClientConfiguration.Settings.ActiveDirectoryTenant;
string clientAppId = WebServiceClientConfiguration.Settings.ClientAppId;
string clientKey = WebServiceClientConfiguration.Settings.ClientKey;
string aadResource = WebServiceClientConfiguration.Settings.ActiveDirectoryResource;

AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant);
ClientCredential clientCredential = new ClientCredential(clientAppId, clientKey);
UserPasswordCredential upc = new UserPasswordCredential(WebServiceClientConfiguration.Settings.UserName, WebServiceClientConfiguration.Settings.Password);

AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(aadResource, clientAppId, upc);

return authenticationResult.CreateAuthorizationHeader();

Applications provisioned in Azure AD are not enabled to use the OAuth2 implicit grant by default. You need to explicitly opt in - more details can be found here: Azure AD OAuth2 implicit grant