AzureAD / microsoft-authentication-library-for-dotnet

Microsoft Authentication Library (MSAL) for .NET
https://aka.ms/msal-net
MIT License
1.36k stars 330 forks source link

[Bug] PlatformNotSupportedException in HTTP client using PublicClientApplication in .NET 8 Blazor WASM #4723

Closed Awiprogs-AW closed 2 months ago

Awiprogs-AW commented 2 months ago

Library version used

8.0.3

.NET version

.NET 8 Blazor WASM

Scenario

Other - please specify

Is this a new or an existing app?

This is a new app or experiment

Issue description and reproduction steps

Hello, is there any possibility to use the PublicClientApplication in Blazor WASM? I’m using .NET 8.

This is working both on WPF and as PoC in Web.API .NET 8. Unfortunately in my Blazor project I’m always getting exception: System.PlatformNotSupportedException: Operation is not supported on this platform. at System.Net.Http.BrowserHttpHandler.set_Credentials(ICredentials value) at System.Net.Http.HttpClientHandler.set_UseDefaultCredentials(Boolean value) at Microsoft.Identity.Client.PlatformsCommon.Shared.SimpleHttpClientFactory.InitializeClient() at System.Lazy1[[System.Net.Http.HttpClient, System.Net.Http, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]]. ViaFactory(LazyThreadSafetyMode mode) at System.Lazy1[[System.Net.Http.HttpClient, System.Net.Http, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]]. ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) at System.Lazy1[[System.Net.Http.HttpClient, System.Net.Http, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]]. CreateValue() at System.Lazy1[[System.Net.Http.HttpClient, System.Net.Http, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].get_Value() at Microsoft.Identity.Client.PlatformsCommon.Shared.SimpleHttpClientFactory.GetHttpClient() at Microsoft.Identity.Client.Http.HttpManagerWithRetry.GetHttpClient() at Microsoft.Identity.Client.Http.HttpManager.<>cDisplayClass15_1. <b2>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.Identity.Client.Utils.StopwatchService. d51[[System.Net.Http.HttpResponseMessage, System.Net.Http, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]]. MoveNext() at Microsoft.Identity.Client.Http.HttpManager.ExecuteAsync(Uri endpoint, IDictionary2 headers, HttpContent body, HttpMethod method, ILoggerAdapter logger, CancellationToken cancellationToken) at Microsoft.Identity.Client.Http.HttpManagerWithRetry.SendRequestAsync(Uri endpoint, IDictionary2 headers, HttpContent body, HttpMethod method, ILoggerAdapter logger, Boolean doNotThrow, Boolean retry, CancellationToken cancellationToken) at Microsoft.Identity.Client.OAuth2.OAuth2Client. <ExecuteRequestAsync>d__121[[Microsoft.Identity.Client.Instance.Discovery.InstanceDiscoveryResponse, Microsoft.Identity.Client, Version=4.60.3.0, Culture=neutral, PublicKeyToken=0a613f4dd989e8ae]]. MoveNext() at Microsoft.Identity.Client.Instance.Discovery.NetworkMetadataProvider.SendInstanceDiscoveryRequestAsync(Uri authority, RequestContext requestContext) at Microsoft.Identity.Client.Instance.Discovery.NetworkMetadataProvider.FetchAllDiscoveryMetadataAsync(Uri authority, RequestContext requestContext) at Microsoft.Identity.Client.Instance.Discovery.NetworkMetadataProvider.GetMetadataAsync(Uri authority, RequestContext requestContext) at Microsoft.Identity.Client.Instance.Discovery.InstanceDiscoveryManager.FetchNetworkMetadataOrFallbackAsync(RequestContext requestContext, Uri authorityUri) at Microsoft.Identity.Client.Instance.Discovery.InstanceDiscoveryManager.GetMetadataEntryAsync(AuthorityInfo authorityInfo, RequestContext requestContext, Boolean forceValidation) at Microsoft.Identity.Client.Instance.AuthorityManager.RunInstanceDiscoveryAndValidationAsync() at Microsoft.Identity.Client.Internal.Requests.InteractiveRequest.ExecuteAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.RequestBase.<>c__DisplayClass11_1. <b1>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.Identity.Client.Utils.StopwatchService.MeasureCodeBlockAsync(Func`1 codeBlock) at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.ApiConfig.Executors.PublicClientExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenInteractiveParameters interactiveParameters, CancellationToken cancellationToken)

Relevant code snippets

IPublicClientApplication app = PublicClientApplicationBuilder
                    . Create(ClientId)
                    . WithAuthority(Authority)
                    . WithDefaultRedirectUri()
                    . Build();

  try
  {
      var test = app.AcquireTokenInteractive(scopes);

      var response = await test.ExecuteAsync();
  }
  catch (Exception exc)
  {
      Console.WriteLine(exc);
  }

Expected behavior

I'd like to get a response instead of the PlatformNotSupportedException.

Identity provider

Microsoft Entra ID (Work and School accounts and Personal Microsoft accounts)

Regression

No response

Solution and workarounds

No response

bgavrilMS commented 2 months ago

Interesting. It looks like MSAL's HttpClient isn't compatible with WASM. Luckely you can provide your own

https://learn.microsoft.com/en-us/entra/msal/dotnet/advanced/httpclient#example-implementation

pmaytak commented 2 months ago

Blazor WASM is a web app framework, no? So you should use ConfidentialClientApplication.

See Public client and confidential client applications for differences between two types.

There's a Blazor WASM sample here that uses MSAL JavaScript and a Blazor Server sample here which actually uses Microsoft.Identity.Web - an simplified abstraction over MSAL.NET (recommended).

Awiprogs-AW commented 2 months ago

I didn't ask for explanation what Blazor WASM is and have already implemented the "right" MSAL solution using MSAL.js and MS Graph. But there are problems with it and I wanted to have full control over authentication like I have with PublicClientApplication in e.g. WPF or Web.API. I don't want to have some additional stuff done with Cookies, Session Storage etc. behind me. PublicClientApplication gives me the possibility for the same process with same sign-in dialog and all the things it offers but finally returning a response with which I can do what I want. You can also see that WPF is in first group and Web.API (confidential app) in the second one and in both cases I could test a PoC with PublicClientApplication so I don't know what you were going to point me out. I don't need to go into security details here. I'd no like to explain all the details from the current implementation as it will take too much time, but logging-out and leaving "trash" somewhere makes my application working incorrectly for personal accounts especially after logging out. Generally using RemoteAuthenticatorView and AddAccountClaimsPrincipalFactory is what I'd like to avoid and that's the reason why I tried to implement something what just returns me a log-in result with which I can do whatever I want within the AuthenticationStateProvider. The provided solution https://github.com/Azure-Samples/ms-identity-blazor-wasm/tree/main/WebApp-graph-user/Call-MSGraph seems to be a tricky one for me and will not solve the problems I have. My solution would be almost fine unless await graphClient.Me.RevokeSignInSessions.PostAsRevokeSignInSessionsPostResponseAsync(); worked for all account types including personal accounts. Unfortunately, clean log out works only for work accounts. https://learn.microsoft.com/en-us/graph/api/user-revokesigninsessions?view=graph-rest-1.0&tabs=http