-
Notifications
You must be signed in to change notification settings - Fork 254
Description
Microsoft.Identity.Web Library
Microsoft.Identity.Web
Microsoft.Identity.Web version
4.5.0
Web app
Not Applicable
Web API
Not Applicable
Token cache serialization
Not Applicable
Description
The symptom of this bug is a "no client ID" error when making concurrent requests using the GraphServiceClient. The reason for this seems to be that the GraphServiceClient instances all share a singleton instance of the internal MergedOptions class using the ConcurrentDictionary in MergedOptionStore and the ConfidentialClientApplicationOptions property initialization is not thread-safe.
The MergedOptions class has a ConfidentialClientApplicationOptions property, and in the getter it will:
- Check if the _confidentialClientApplicationOptions class variable is null
- If true, it will:
- Assign the _confidentialClientApplicationOptions class variable to a new instance
- Begin updating values on the _confidentialClientApplicationOptions instance
- Return the_confidentialClientApplicationOptions class variable
The problem seemingly would occur when:
- Two threads access the ConfidentialClientApplicationOptions property on the singleton MergedOptions at nearly the same time
- The first thread has seen the class variable is null, assigned a new instances to that class variable, but not completed updating values
- The second thread accesses the property and returns the class variable before the values have been updated
The result is the second thread would return an empty ConfidentialClientApplicationOptions instance.
Reproduction steps
This is a concurrency race condition, so there's not a way to reproduce this consistently across different machines. However, it should be possible by kicking off two threads that make a call to the GraphServiceClient that will initialize MergedOptions and then adjusting the offset of their execution times.
Error message
Error:
"ErrorType": "Microsoft.Identity.Client.MsalClientException",
"ErrorMessage": "No ClientId was specified. ",
"IsNonRetriable": false,
"Properties": {
"ErrorCode": "no_client_id"
}
Stack trace:
at Microsoft.Identity.Client.AbstractApplicationBuilder.Validate()
at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.Validate()
at Microsoft.Identity.Client.AbstractApplicationBuilder.BuildConfiguration()
at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.BuildConcrete()
at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.Build()
at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplicationAsync(MergedOptions mergedOptions)
at Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplicationAsync(MergedOptions mergedOptions)
at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(String scope, String authenticationScheme, String tenant, TokenAcquisitionOptions tokenAcquisitionOptions)
at Microsoft.Identity.Web.DefaultAuthorizationHeaderProvider.CreateAuthorizationHeaderAsync(IEnumerable scopes, AuthorizationHeaderProviderOptions downstreamApiOptions, ClaimsPrincipal claimsPrincipal, CancellationToken cancellationToken)
at Microsoft.Identity.Web.GraphAuthenticationProvider.AuthenticateRequestAsync(RequestInformation request, Dictionary additionalAuthenticationContext, CancellationToken cancellationToken)
at Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.GetHttpResponseMessageAsync(RequestInformation requestInfo, CancellationToken cancellationToken, Activity activityForAttributes, String claims, Boolean isStreamResponse)
at Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.SendAsync[ModelType](RequestInformation requestInfo, ParsableFactory factory, Dictionary errorMapping, CancellationToken cancellationToken)
at Microsoft.Graph.Drives.Item.Items.Item.DriveItemItemRequestBuilder.GetAsync(Action requestConfiguration, CancellationToken cancellationToken)
at Cosmos.Entities.Items.CosmosGraphServiceClient.GetDriveItemAsync(String driveId, String itemId) in /src/Cosmos/Cosmos.Entities.Items/CosmosGraphServiceClient.cs:line 258
at Cosmos.Entities.Items.DriveItemContext.GetItemByIdAsync(String id) in /src/Cosmos/Cosmos.Entities.Items/DriveItemContext.cs:line 144
at Cosmos.Entities.Items.ItemCollectionContext.GetItemAsync(ItemRef itemRef) in /src/Cosmos/Cosmos.Entities.Items/ItemCollectionContext.cs:line 24
at Cosmos.Data.DataSources.StaticFile.WorkbookProvider.GetWorkbook(String tenantId, String environmentId, ItemRef itemRef) in /src/Cosmos/Cosmos.Data.DataSources/StaticFile/WorkbookProvider.cs:line 77
at Cosmos.Data.DataSources.StaticFile.StaticFileDataSource.GetTablePager(SelectTableQuery selectTableQuery, Int32 pageSize) in /src/Cosmos/Cosmos.Data.DataSources/StaticFile/StaticFileDataSource.cs:line 85
at Cosmos.Etl.Workflows.DataSource.DataSourceWorkflow.__DisplayClass26_0.__0.MoveNext() in /src/Cosmos/Cosmos.Etl/Workflows/DataSource/DataSourceWorkflow.cs:line 299
Id Web logs
No response
Relevant code snippets
microsoft-identity-web/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs
Lines 31 to 43 in 6e93817
| public ConfidentialClientApplicationOptions ConfidentialClientApplicationOptions | |
| { | |
| get | |
| { | |
| if (_confidentialClientApplicationOptions == null) | |
| { | |
| _confidentialClientApplicationOptions = new ConfidentialClientApplicationOptions(); | |
| UpdateConfidentialClientApplicationOptionsFromMergedOptions(this, _confidentialClientApplicationOptions); | |
| } | |
| return _confidentialClientApplicationOptions; | |
| } | |
| } |
Regression
No response
Expected behavior
No error occurs when accessing the GraphServiceClient from multiple threads.