Identity Identity - 16 days ago 4
C# Question

B2B access in multi tenant application

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.

Answer

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);
}