We have an ASP.Net Core application with Microsoft Graph integration which we are using to log users in with their Microsoft accounts, access their calendars etc.
We are also in the process of implementing Microsoft's OneDrive FilePicker so that users can view/select files on both their OneDrive and Sharepoint Sites:
https://learn.microsoft.com/en-us/onedrive/developer/controls/file-pickers/?view=odsp-graph-online
For graph, we use token acquisition to get an access token for the GraphServiceClient. The setup of this in startup.cs looks like so:
var initialScopes = configuration.GetValue<string>("Authentication:DownstreamApi:Scopes")?.Split(' ');
authenticationBuilder.AddMicrosoftIdentityWebApp(configuration.GetSection("Authentication:AzureAd"),
OpenIdConnectDefaults.AuthenticationScheme)
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(configuration.GetSection("Authentication:DownstreamApi"))
.AddDistributedTokenCaches();
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = distributedCacheConnection.ConnectionString;
options.SchemaName = "dbo";
options.TableName = "MicrosoftTokenCache";
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
Whenever we want to use the Graph client, we get the token from the token acquisition using our own SignedInUserTokenProvider:
var signedInAuthProvider = new BaseBearerTokenAuthenticationProvider(new
SignedInUserTokenProvider(_tokenAcquisition));
_signedInUserGraphClient = new GraphServiceClient(signedInAuthProvider);
SignedInUserTokenProvider.cs:
public class SignedInUserTokenProvider : IAccessTokenProvider
{
private readonly ITokenAcquisition _tokenAcquisition;
public SignedInUserTokenProvider(ITokenAcquisition tokenAcquisition)
{
_tokenAcquisition = tokenAcquisition;
}
public async Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object> additionalAuthenticationContext = default,
CancellationToken cancellationToken = default)
{
return await _tokenAcquisition.GetAccessTokenForUserAsync
(
new string[] { },
authenticationScheme: OpenIdConnectDefaults.AuthenticationScheme
);
}
public AllowedHostsValidator AllowedHostsValidator { get; }
}
This works perfectly. We are able to work with Graph and all the features it includes.
We have also got the OneDrive file picker working. This requires two slightly different tokens (One for OneDrive and one for SharePoint) so a slightly different call to the token Acquisition is made with different scopes
The OneDrive token call (sharepoint address obscured for obvious reasons):
var pickerSite = new Uri("https://companyname-my.sharepoint.com");
string[] scopes = new string[] {
$"https://{pickerSite.DnsSafeHost}/AllSites.FullControl" };
var token = await _tokenAcquisition.GetAccessTokenForUserAsync
(
scopes,
authenticationScheme: OpenIdConnectDefaults.AuthenticationScheme
);
return token;
A separate SharePoint call is made which is identical to the above, except instead of https://companyname-my.sharepoint.com
the scope is set to https://companyname.sharepoint.com
(where companyname
is set to our organisation name/tenant).
This again works great, and we have the OneDrive picker working and able to access OneDrive and SharePoint through our application.
The 3 tokens (one for graph, 2 for SharePoint) all get stored in our distributed SQL cache. And this is where the problem occurs. After getting a SharePoint token, the Graph token retrievals no longer works, resulting in the following error:
Multiple access tokens found for matching authority, client_id, user and scopes.
{"The cache contains multiple tokens satisfying the requirements. Try to clear token cache. "}
There are multiple tokens cached for each user, however, we are expecting this. All 3 tokens have different scopes as they are accessing different resources for the logged-in user. We need to be able to retrieve and cache tokens for different resources, especially as it is not possible to retrieve tokens from different resources in the same token request call.
Why can't 3 different access tokens be stored in cache together, and how we could go about resolving the above situation?
Jalpa Panchal said:
The error you are facing indicates that there are multiple eligible tokens in the cache. this issue may be due to the fact that the token caching mechanism is not able to properly distinguish between tokens of different scopes. Typically, tokens are stored and retrieved based on factors such as user accounts, client IDs, permissions, and scopes. If these factors are similar or identical, the caching mechanism may confuse different tokens.Make sure each token has a unique key in the cache.use custom key generation logic for the token cache to more clearly distinguish between different tokens.
I was not being specific enough with the token scope that I required in the SignedInUserTokenProvider
token acquisition, and instead, I was passing an empty string array of scopes.
This was satisfying the criteria of all 3 tokens, and so the error of multiple tokens found occurred.
By passing in the scopes that I require here instead (i.e., the scopes listed in our main config), the token acquisition only returns the one token.